1

我正在尝试了解SFINAE(我正在关注本教程),但有一些......“设计选择”我不明白,因此,我发现它们令人困惑。

假设我有这样的情况(包括重新实现std::enable_ifis there 只是为了展示我的理解enable_if

// A default class (class type) declaration. Nothing unusual.
template <bool, typename T = void>
struct enable_if
{}; 

// A specialisation for <true, T> case. I understand 'why-s' of this. 
// -- 'why-s': if I attempt to access 'enable_if<false, T>::type' (which does not exist) I will get a substitution failure and compiler will just "move-on" trying to match "other cases".
template <typename T>
struct enable_if<true, T> {
    typedef T type;
};

// Here lies my problem:
template <class T,
         typename std::enable_if<std::is_integral<T>::value,T>::type* = nullptr>
void do_stuff(T& t) { /* do stuff */ };


(1)我有一个“问题”的第一件事是bool文字 ( true/ false)。我知道它们是正确的,模板可以接受原始数据类型(普通旧数据类型)的编译时常量值,但如果我的任务是设计enable_if“机制”而不是使用true/false我会创建一个标记类true_t(或True)和false_t(或False)如下:

class true_t {}; // or True

class false_t {}; // or False

template<typename T>
class is_integral // just to have "something" to use with "enable_if"
{
    using result = false_t;
};

template<>
class is_integral<int32_t> // same with all other int types
{
    using result = true_t;
};

template <typename B, typename T = void>
struct enable_if
{}; 

template <typename T>
struct enable_if<true_t, T>
{
    using type = T;
};


(2)我觉得多余的第二件事是需要指定typename T模板参数。enable_if如下实现会不会更容易/更好:

template <typename B>
struct enable_if
{}; 

template <>
struct enable_if<true_t>
{
    using type = void; // the 'type' exists therefore substitution failure will not occur. 
};


我很清楚我所有的提议都比目前现有的解决方案差很多,但我不明白为什么......SFINAE我剃掉了当前的功能(重要功能)的哪一部分?(甚至没有意识到……)


我知道,在这个网站上,我有义务以......单一的“类似问题的”格式提出一个问题,但如果你认为它可以接受,我还可以问一下这种语法是什么:

std::enable_if</* ... */>::type* = nullptr

完成?现在已经超出了我的理解范围...

4

2 回答 2

4

我有一个“问题”的第一件事是bool文字(true/ false)。我知道它们是正确的,模板可以接受原始数据类型(普通旧数据类型)的编译时常量值,但如果我的任务是设计enable_if“机制”而不是使用true/false我会创建一个标记类true_t(或True)和false_t(或False)如下

使用标签类型而不是布尔类型的问题是您必须为代码增加额外的复杂性。如果你想检查编译时条件,sizeof例如,你不能只做sizeof(T) == 8. 您必须进行一个抽象来进行检查并返回适当的标签类型。

我发现多余的第二件事是需要指定typename T模板参数。enable_if如下实现不是更容易/更好吗

并不真地。如果你想使用 SFINAE 作为返回类型怎么办?那时你只能有一个 void 函数,这是不必要的限制。相反,您可以使用后来在 C++14 和 C++17 中添加的内容并制作别名。这使名称不依赖,并允许您删除typename

template< bool B, class T = void >
using enable_if_t = typename enable_if<B,T>::type;
template< class T >
inline constexpr bool is_integral_v = is_integral<T>::value;

这允许您重写

template <class T,
         typename std::enable_if<std::is_integral<T>::value,T>::type* = nullptr>
void do_stuff(T& t) { /* do stuff */ };

template <class T,
         std::enable_if_t<std::is_integral_v<T>,T>* = nullptr>
void do_stuff(T& t) { /* do stuff */ };

虽然我更喜欢使用 bool 来表示enable_if_tlike

template <class T,
         std::enable_if_t<std::is_integral_v<T>, bool> = true>
void do_stuff(T& t) { /* do stuff */ };

我知道,在这个网站上,我有义务以......单一的“类似问题的”格式提出一个问题,但如果你认为它可以接受,我还可以问一下这种语法是什么:

std::enable_if</* ... */>::type* = nullptr

完成?

它创建一个指向std::enable_if“返回”类型的指针并将其设置为空指针。这里的目标是制作一个仅在条件为真时才存在的模板参数。您可以将其重写为

typename = typename std::enable_if</* ... */>::type

所以你有一个类型参数,而不是一个非类型参数。它们都完成了同样的事情,但后者不能为不同的 ' 重载函数,enable_if因为默认模板参数不是签名的一部分。第一个使用非类型参数的版本包含在函数签名中,并且允许您重载enable_if's.

于 2018-12-19T14:02:51.290 回答
3

首先存在 和 的标签类型truefalsestd::true_typestd::false_type

假设我们enable_if使用 this 而不是 bool 参数进行了工作。然后你不能再做像std::enable_if<1 == 1>::type因为1 == 1评估为布尔值的事情。大多数你想在这里测试的东西也是如此。

另一方面,现有的标签类型可以在 a 中使用,enable_if因为它们包含 avalue并且具有operator()返回所述值的 a。

所以在我看来,按照你的方式去做会失去很多便利,而且从我所见,什么也不会得到。

enable_if对于第 2 点,如果它是真的,能够指定你想要持有的类型只是一种方便。它默认为void,但如果您愿意,您可以轻松地推导出intdouble。这有时很有用。

于 2018-12-19T14:03:46.030 回答