想象一下,我有一个模板: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()
用作偏移量时,unsigned
或size_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_listener
和using off_t = conditional_t<something,the_type,void>
。我想要的是:
private: off_t start_()
不是public: off_t start()
,则start_()
呼叫off_t
void
无效(或满足某些条件),则没有方法start()
。如果我尝试调用它,可以选择一些off_t
。我正在考虑将该类与扩展程序合并,它将通过将自身(static_assert
)强制转换为真实类(*this
)并重定向调用来发布函数(对于私人方法,扩展器成为朋友)。我创建了另一个rqueue<...>&
助手,它可以继承所选的任何类,同时忽略任何传递的template<class... Bases> class merge
。它有效,但解决方案非常难看,我不喜欢它。
我想到的另一个可能的解决方案是创建一些基本实现(作为一个不同的模板,可能隐藏在某些void
中)并使用一系列模板特化,这些特殊化将根据选项发布方法。问题是组合的数量正在快速增长,并且namespace detail
可能存在另一个问题 - 该类可以访问记录的私有方法(传递给模板的第一个参数)。
我的SFINAE和friend
尝试通常以编译器错误结束,抱怨模板(或部分特化)中不允许方法特化,或者不应该触发static_assert。我希望有一些不错的解决方案。期待看到它:))
答案 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)
利用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: !
使用一些丑陋的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>'
使用一些带有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'