基本问题说明
我正在学习SFINAE。我尝试了一个非常简单的enable_if
:
// 1: A foo() that accepts arguments that are derived from Base
template <typename T, typename Enable = enable_if_t<std::is_base_of_v<Base, T>>>
void foo(T thing) {
std::cout << "It's a derived!" << std::endl;
}
// 2: A foo() that accepts all other arguments
template <typename T, typename Enable = enable_if_t<!std::is_base_of_v<Base, T>>>
void foo(T thing) {
std::cout << "It's not a derived." << std::endl;
}
编译器抱怨foo
被多重定义。互联网告诉我这是因为在检查函数签名时模板参数无关。
我尝试过的变量
在寻求进行最基本的元编程的过程中,我开始着手解决这个问题。这是我为enable_if
语句尝试过的事情的列表(反向语句,即!std::is_base_of
相同,为简洁起见省略了):
匿名类型,否typename
,等于0
https://en.cppreference.com/w/cpp/types/enable_if告诉我,我在上面所做的事情是错误的。但是它的建议(在第一个注释块下)是适当的隐秘的,更重要的是,它也没有编译。
std::enable_if_t<std::is_base_of_v<Base, T>> = 0
匿名类型,否typename
,等于void
认为如果我正在使用类型进行编程,则使用类型将是一个明智的选择,我改为尝试将模板默认设置为void
。没有骰子。
std::enable_if_t<std::is_base_of_v<Base, T>> = void
匿名类型,是typename
,等于void
虽然我们正在向它抛出语法,但是如果我将此模板参数默认为一种类型,我是否应该使用typename关键字?
typename std::enable_if_t<std::is_base_of_v<Base, T>> = void
最后的结果,如此明显地起作用
typename enable_if_t<std::is_base_of_v<Base, T>, T>* = nullptr
我问过每个知道为什么这样做的人,但我的其他变体却不起作用,它们同样令人困惑。我很茫然。更令人困惑的是,如果我命名为这种类型(例如typename Enable = ...
),它将无法编译。
请一位对TMP和enable_if
更为熟悉的人向我解释:
enable_if
声明为指向类型的指针并将其默认设置为nullptr
为何起作用?enable_if
的语义规则是什么?enable_if
产生的命名类型的语义规则是什么?非常感谢。
答案 0 :(得分:3)
第一组变量仅设置模板类型参数的值。模板类型参数的两个具有不同值的重载发生冲突,因为它们都是template<class,class>
类型,并且具有相同的函数参数。
非类型模板参数的情况下,如果最终使用void类型的模板非类型参数,则使用原始启用。那是非法的;各种错误消息是各种非法方法。
添加星号时,如果enable if子句通过,则它是void指针类型的模板非类型参数。
失败时,它根本不是参数。
等效于nullptr casebis:
std::enable_if_t<std::is_base_of_v<Base, T>, bool> = true
当子句为true时,enable if的值为bool
,我们得到:
bool = true
布尔型的模板非类型实参,默认为true。当子句(子句的基数)为false时,我们得到SFINAE失败。那里没有模板类型或非类型参数。
在class Whatever = enable_if
情况下,我们正在基于模板参数的默认值尝试SFINAE。这会导致签名冲突,因为如果在重载解析期间(在同一阶段)找到签名,则它们必须是唯一的。
在enable = value
情况下,我们正在尝试基于 的SFINAE,那里有一个模板非类型参数。失败时,没有要比较的签名,因此不会冲突。
剩下的是使语法简单漂亮。
现在,所有这些都已被Concepts过时了,所以不要爱上语法。