为什么 `SFINAE` (std::enable_if) 使用 bool 文字而不是 `true_t` / `false_t` 标签 类?
Why do `SFINAE` (std::enable_if) uses bool literals instead of `true_t` / `false_t` tag classes?
我正在尝试了解 SFINAE
(我正在关注此 tutorial),但有些... "design choices" 我不明白,因此,我发现它们令人困惑。
假设我遇到这样的情况(包括 std::enable_if
的重新实现只是为了证明我是如何理解 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) 我有一个 "problem" 的第一件事是 bool
文字(true
/false
).我知道它们是正确的,模板可以接受原始数据类型(普通旧数据类型)的编译时常量值,但是如果我的任务是设计 enable_if
"mechanisms" 而不是使用 true
/false
我会创建一个标签 类 true_t
(or True
) and false_t
(or 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
的功能(重要功能)我剃掉了吗? (甚至都没有意识到...)
我知道,在这个网站上,我有义务以...单一 "question-post-like" 格式提出一个问题,但如果您认为可以接受,我也可以问这个语法是什么:
std::enable_if</* ... */>::type* = nullptr
完成了吗?我现在无法理解...
The very 1st thing I have a "problem" with, is bool
literal (true
/false
). I understand they are correct and templates can accept compile-time constant values of primitive data types (plain-old-data types) but if I were tasked to design the enable_if
"mechanisms" instead of using true
/false
I would create a tag classes true_t
(or True
) and false_t
(or False
) as follows
使用标记类型而不是布尔类型的问题是您必须为代码增加额外的复杂性。如果你想检查一个编译时条件,例如 sizeof
,你不能只做 sizeof(T) == 8
。您将不得不进行抽象来进行检查,并且 return 是适当的标记类型。
The second thing I find redundant is the need to specify typename T
template parameter. Wouldn't it be easier / better to just implement enable_if
as follows
不是真的。如果要将 SFINAE 用于 return 类型怎么办?那么你只能有一个 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_t
类型,例如
template <class T,
std::enable_if_t<std::is_integral_v<T>, bool> = true>
void do_stuff(T& t) { /* do stuff */ };
I know that, on this site, I am obligated to ask a single question within a... single "question-post-like" format, but if you find it acceptable could I also ask what will this syntax:
std::enable_if</* ... */>::type* = nullptr
accomplish?
它创建一个指向std::enable_if
"returns" 类型的指针并将其设置为空指针。这里的目标是创建一个只有在条件为真时才会存在的模板参数。您可以将其重写为
typename = typename std::enable_if</* ... */>::type
所以你有一个类型参数而不是一个非类型参数。他们都完成了同样的事情,但后者不会为不同的 enable_if
重载函数,因为默认模板参数不是签名的一部分。使用非类型参数的第一个版本包含在函数签名中,并允许您重载 enable_if
。
首先存在 true
和 false
的标签类型,即 std::true_type
和 std::false_type
。
假设我们让 enable_if
使用这个而不是 bool 参数。你不能再做像 std::enable_if<1 == 1>::type
这样的事情,因为 1 == 1
的计算结果是布尔值。您想要在这里测试的大多数事情也是如此。
另一方面,现有的标签类型可以在 enable_if
中使用,因为它们包含 value
并且具有 operator()
表示 return 的值。
所以在我看来,按照你的方式做会失去很多便利,据我所知,什么也得不到。
对于第 2 点,能够指定您希望 enable_if
持有的类型(如果为真)只是一种便利。它默认为 void
,但如果您愿意,可以轻松地让它推断出 int
、double
等。这有时很有用。
我正在尝试了解 SFINAE
(我正在关注此 tutorial),但有些... "design choices" 我不明白,因此,我发现它们令人困惑。
假设我遇到这样的情况(包括 std::enable_if
的重新实现只是为了证明我是如何理解 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) 我有一个 "problem" 的第一件事是 bool
文字(true
/false
).我知道它们是正确的,模板可以接受原始数据类型(普通旧数据类型)的编译时常量值,但是如果我的任务是设计 enable_if
"mechanisms" 而不是使用 true
/false
我会创建一个标签 类 true_t
(or True
) and false_t
(or 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
的功能(重要功能)我剃掉了吗? (甚至都没有意识到...)
我知道,在这个网站上,我有义务以...单一 "question-post-like" 格式提出一个问题,但如果您认为可以接受,我也可以问这个语法是什么:
std::enable_if</* ... */>::type* = nullptr
完成了吗?我现在无法理解...
The very 1st thing I have a "problem" with, is
bool
literal (true
/false
). I understand they are correct and templates can accept compile-time constant values of primitive data types (plain-old-data types) but if I were tasked to design theenable_if
"mechanisms" instead of usingtrue
/false
I would create a tag classestrue_t
(orTrue
) andfalse_t
(orFalse
) as follows
使用标记类型而不是布尔类型的问题是您必须为代码增加额外的复杂性。如果你想检查一个编译时条件,例如 sizeof
,你不能只做 sizeof(T) == 8
。您将不得不进行抽象来进行检查,并且 return 是适当的标记类型。
The second thing I find redundant is the need to specify
typename T
template parameter. Wouldn't it be easier / better to just implementenable_if
as follows
不是真的。如果要将 SFINAE 用于 return 类型怎么办?那么你只能有一个 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_t
类型,例如
template <class T,
std::enable_if_t<std::is_integral_v<T>, bool> = true>
void do_stuff(T& t) { /* do stuff */ };
I know that, on this site, I am obligated to ask a single question within a... single "question-post-like" format, but if you find it acceptable could I also ask what will this syntax:
std::enable_if</* ... */>::type* = nullptr
accomplish?
它创建一个指向std::enable_if
"returns" 类型的指针并将其设置为空指针。这里的目标是创建一个只有在条件为真时才会存在的模板参数。您可以将其重写为
typename = typename std::enable_if</* ... */>::type
所以你有一个类型参数而不是一个非类型参数。他们都完成了同样的事情,但后者不会为不同的 enable_if
重载函数,因为默认模板参数不是签名的一部分。使用非类型参数的第一个版本包含在函数签名中,并允许您重载 enable_if
。
首先存在 true
和 false
的标签类型,即 std::true_type
和 std::false_type
。
假设我们让 enable_if
使用这个而不是 bool 参数。你不能再做像 std::enable_if<1 == 1>::type
这样的事情,因为 1 == 1
的计算结果是布尔值。您想要在这里测试的大多数事情也是如此。
另一方面,现有的标签类型可以在 enable_if
中使用,因为它们包含 value
并且具有 operator()
表示 return 的值。
所以在我看来,按照你的方式做会失去很多便利,据我所知,什么也得不到。
对于第 2 点,能够指定您希望 enable_if
持有的类型(如果为真)只是一种便利。它默认为 void
,但如果您愿意,可以轻松地让它推断出 int
、double
等。这有时很有用。