(可选)基于可变模板参数发布方法

Optionally publish methods based on variadic template parameters

本文关键字:布方法 方法 参数 可选 于可变      更新时间:2023-10-16

前言

想象一下,我有一个模板:template<class... Opts> class rqueue,它可以具有通过传递到参数列表的标签(特殊选项结构)选择的各种功能,例如

rqueue<trace_record, opt::fixed_size<>> trace;
rqueue<trace_record::flat, opt::fixed_size<>, opt::virtual_offset<>> info;

第一个版本(trace)是用于写入跟踪记录的记录队列(opt::fixed_size将其大小限制为4096B)。第二个版本(info)将从第一个版本开始填充(由一些线程重写记录,并转换为平面表示),但重要的是opt::virtual_offset<>添加了以下方法:

off_t start(); // virtual offset of oldest record
off_t stop();  // virtual offset of future record (when next gets written)

以及基于该虚拟偏移的各种其他功能(offset_to_iterator()),该虚拟偏移总是在增长(随着每个记录),其模拟大小为例如4GB的虚拟存储器(当unsigned用作偏移时,它可以使用size_tunisgned long long更大),其中实际缓冲区(大小为例如4096B)在该虚拟存储器内创建窗口

链接到我的另一个相关问题-选项包帮助程序,它是专门为这个模板设计的

(请注意,可能还有许多其他功能可以独立组合,例如opt::rqueue_listener,可用于报告各种事件)

问题

我已经设法创建了具有所有可能功能的模板,其中一些方法在未选择功能时是(例如,在这种情况下,start()返回零,stop()size()相同),但如果未选择功能,我希望以某种方式隐藏这些方法知道吗

(如果不包括opt::rqueue_listener,则另一个示例为伪set_listener(void*)-该选项可以与任何其他选项组合。)

编辑:想象一下,例如using off_t = conditional_t<something,the_type,void>private: off_t start_()。我想要的是:

  1. 如果off_t不是void,则让public: off_t start()调用该start_()
  2. 如果off_t无效(或满足某些条件),则没有方法start()。或者,如果我尝试调用static_assert

我的尝试

我正在考虑将该类与扩展程序合并,后者将通过将自身(*this)强制转换为真实类(rqueue<...>&)并将调用重定向到那里(重定向到私有方法,在那里扩展器是朋友)来发布函数。我创建了另一个template<class... Bases> class merge帮助程序,它可以继承任何选定的类,同时忽略任何传递的void。它起了作用,但解决方案相当丑陋,我不喜欢它。

我想到的另一个可能的解决方案是创建一些基本的实现(作为一个不同的模板,可能隐藏在一些namespace detail中),并使用一系列模板专门化来发布基于选项的方法。问题是组合的数量正在快速增长,friend允许类访问记录的私有方法(传递给模板的第一个参数)可能会出现另一个问题。

我的SFINAE和static_assert尝试经常以编译器错误告终,抱怨模板中不允许方法专门化(或部分专门化),或者static_assert在不应该被激发的时候被激发了。我希望有一些不错的解决方案。期待看到它:)

以下代码是我在收到Piotr S.的提示后所做的尝试:
(想象一下using namespace std在标题中,尽管它有点不同。)

#include "basics.hpp"
using namespace firda;
template<class Offset = void> struct helper {
    Offset start_() const { return 0; }
};
template<> struct helper<void> {
    void start_() const {}
};
template<class Offset = void> class rqueue
  : private helper<Offset> {
public:
    Offset start() const {
        static_assert(!is_same<Offset,void>::value, "!!");
        return this->helper<Offset>::start_();
    }
};
int main() {
    rqueue<> one;
    rqueue<uint> two;
    cout << two.start();
//  one.start(); -- assert triggered
}

我在实际代码中遇到了类似static_assert的一些问题,但不记得编译器为什么在基本版本中触发它。。。。可能是我的错误,在不该调用它的地方调用了它。我认为有一个start_()用于内部使用(如果不使用,可以伪造它),有一个带有断言的public: start()(并确保不要在模板中调用它)可以解决问题。我会等一段时间(如果有不同的答案,我很乐意接受)。

选项1

利用static_assert以及模板的成员函数仅在上下文需要时按需实例化的事实:

#include <iostream>
#include <type_traits>
constexpr bool condition = true;
template <class... Opts>
class rqueue
{
    using off_t = std::conditional_t<condition, int, void>;
public:
    off_t start()
    {
        static_assert(!std::is_same<off_t, void>::value, "!");
        return start_();
    }
private:
    off_t start_()
    {
        std::cout << "start_()" << std::endl;
        return 1;
    }
};

也就是说,当condition为假时访问start()将触发静态断言错误:

error: static assertion failed: !

演示1

选项2

使用一些丑陋的SFINAE,通过使成员函数也成为模板:

#include <iostream>
#include <type_traits>
constexpr bool condition = true;
template <class... Opts>
class rqueue
{
    using off_t = std::conditional_t<condition, int, void>;
public:
    template <typename T = void>
    std::enable_if_t<!std::is_same<off_t, void>::value, off_t> start()
    {
        return start_();
    }
private:
    off_t start_()
    {
        std::cout << "start_()" << std::endl;
        return 1;
    }
};

这将触发错误:

error: no type named 'type' in 'struct std::enable_if<false, void>'

演示2

选项3

在CRTP中使用一些辅助基类,它有条件地实现方法:

#include <iostream>
#include <type_traits>
constexpr bool condition = true;
template <bool condition, typename CRTP>
struct start_base {};
template <typename CRTP>
struct start_base<true, CRTP>
{    
    int start()
    {
        return static_cast<CRTP*>(this)->start_();
    }
};
template <class... Opts>
class rqueue : public start_base<condition, rqueue<Opts...>>
{
    friend class start_base<condition, rqueue<Opts...>>;
private:
    int start_()
    {
        std::cout << "start_()" << std::endl;
        return 1;
    }
};

condition = false;上的错误消息非常清楚:

error: 'class rqueue<>' has no member named 'start'

演示3