【问题标题】:Optionally publish methods based on variadic template parameters可选择发布基于可变参数模板参数的方法
【发布时间】:2014-10-13 09:34:04
【问题描述】:

前言

假设我有一个模板: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 表示),但重要的是 opt::virtual_offset&lt;&gt; 添加这些方法:

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

以及基于这个始终在增长(每条记录)的虚拟偏移量的各种其他功能(offset_to_iterator()),它模拟大小的虚拟内存,例如4 GB(当unsigned 用作偏移量时,使用size_tunisgned long long 可能会更大),其中实际缓冲区(大小例如4096B)在该虚拟内存中创建一个窗口记忆。

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

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

问题

我已经设法创建了具有所有可能功能的模板,其中某些方法在未选择该功能时是 dummy (例如,start() 返回零,stop() 与 @ 相同987654336@ 在这种情况下),但如果未选择该功能,我想以某种方式隐藏这些方法。 有什么想法吗?

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

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

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

我的尝试

我正在考虑将类与 extenders 合并,这将通过将自身 (*this) 转换为真实类 (rqueue&lt;...&gt;&amp;) 并将调用重定向到那里(到私有方法)来发布函数, extender 成为朋友)。我创建了另一个 template&lt;class... Bases&gt; class merge 助手,它可以继承任何选择的类,同时忽略任何传递的 void。它有效,但解决方案很丑陋,我不喜欢它。

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

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

【问题讨论】:

  • 是 opt::features 总是带有一个模板类型参数的模板吗?
  • 每个特征都是一个结构(可能是带有其他参数的模板,但作为类传递,而不是作为模板传递)。我不认为那是那么重要。有关详细信息,请参阅链接的问题。
  • 例如fill_empty 接受两个参数 - 类型和值:template &lt;class T, T V&gt; struct fill_empty: tag::fill_empty { typedef T type; static constexpr T value = V; };
  • 好吧,也许我不明白你的目标是什么,简而言之:你总是实现 start() 成员函数,但是当你的类继承自 virtual_offset&lt;&gt; 时,你希望它调用 virtual_offset&lt;&gt;::start() ,否则无法访问?
  • 为什么static_assert(!std::is_same&lt;off_t, void&gt;::value, "!"); 在公共start() 成员函数中不是一个选项?

标签: c++ template-meta-programming


【解决方案1】:

以下代码是我在 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()(并确保不要在模板中调用它)解决了这个问题。我会等一会儿(如果有不同的答案,我很乐意接受)。

【讨论】:

    【解决方案2】:

    选项 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: !
    

    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

    【讨论】:

    • 第三个版本在某种程度上类似于我使用那些 extenders 的工作解决方案,但看起来更好(通过传递条件而不是我的 merge&lt;conditional_t&lt;condition,rqueue&lt;Opts...&gt;,void&gt;&gt;)并且完全满足要求(通过如果不需要,则没有方法)。非常感谢。
    猜你喜欢
    • 2016-12-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-05-08
    • 2022-09-22
    • 1970-01-01
    • 2021-02-11
    • 1970-01-01
    相关资源
    最近更新 更多