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

时间:2014-10-13 09:34:04

标签: c++ template-meta-programming

前言

想象一下,我有一个模板: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)将从第一个版本(由某个线程填充,将通过转换为 flat 表示重写记录)填充,但重要的是{{1添加这些方法:

opt::virtual_offset<>

以及基于此虚拟偏移的各种其他功能(off_t start(); // virtual offset of oldest record off_t stop(); // virtual offset of future record (when next gets written) ),这些功能始终在增长(每条记录),模拟大小的虚拟内存,例如: 4 GB(当offset_to_iterator()用作偏移量时,unsignedsize_t可能更大,其中实际缓冲区(大小为4096B)创建窗口< / em>在虚拟内存中。

Link to my other related question - option pack helper专为此模板设计。

(请注意,可能有许多其他功能可以独立组合,例如unisgned long long可用于报告各种事件。

问题

我设法创建了具有所有可能功能的模板,其中一些方法在未选择功​​能时虚拟(例如opt::rqueue_listener返回零并且start()在这种情况下与stop()相同,但如果未选择该功能,我想以某种方式隐藏方法。 有什么想法吗?

(如果未包含size(),则另一个例子是虚拟set_listener(void*) - 该选项可以与任何其他选项结合使用。)

编辑:想象一下,例如opt::rqueue_listenerusing off_t = conditional_t<something,the_type,void>。我想要的是:

  1. 如果private: off_t start_()不是public: off_t start(),则start_()呼叫off_t
  2. 如果void无效(或满足某些条件),则没有方法start()。如果我尝试调用它,可以选择一些off_t
  3. 我的尝试

    我正在考虑将该类与扩展程序合并,它将通过将自身(static_assert)强制转换为真实类(*this)并重定向调用来发布函数(对于私人方法,扩展器成为朋友)。我创建了另一个rqueue<...>&助手,它可以继承所选的任何类,同时忽略任何传递的template<class... Bases> class merge。它有效,但解决方案非常难看,我不喜欢它。

    我想到的另一个可能的解决方案是创建一些基本实现(作为一个不同的模板,可能隐藏在某些void中)并使用一系列模板特化,这些特殊化将根据选项发布方法。问题是组合的数量正在快速增长,并且namespace detail可能存在另一个问题 - 该类可以访问记录的私有方法(传递给模板的第一个参数)。

    我的SFINAE和friend尝试通常以编译器错误结束,抱怨模板(或部分特化)中不允许方法特化,或者不应该触发static_assert。我希望有一些不错的解决方案。期待看到它:))

2 个答案:

答案 0 :(得分:2)

以下代码是我在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 :(得分:1)

选项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;
    }
};

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

error: static assertion failed: !

DEMO 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>'

DEMO 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'

DEMO 3