为什么编译器在使用检测惯用语时不提示双重定义?
Why doesn't compiler prompt double definition when using detection idiom?
让我们来具体一点:
#include <utility>
#include <vector>
template <typename ... Ts>
using void_t = void;
template <typename T, typename = void_t<>>
struct is_lt_comparable : std::false_type {};
template <typename T>
struct is_lt_comparable<T, void_t<decltype(std::declval<T>() < std::declval<T>())>> : std::true_type {};
template <typename T>
static constexpr bool is_lt_comparable_v = is_lt_comparable<T>::value;
struct test{};
int main()
{
#define error_message "detection doesn't work correctly"
static_assert(is_lt_comparable_v<int>, error_message);
static_assert(!is_lt_comparable_v<test>, error_message);
static_assert(is_lt_comparable_v<std::vector<int>>, error_message);
}
在上面的代码中,为什么 first 和 last 断言没有触发 is_lt_comparable
的双重定义?
void_t
带任何参数仍然是 void
。因此,模板的最后一个未命名参数始终是 void
。 IIRC 类型别名不被认为是不同的类型,因此我的直觉让我相信我遗漏了一些东西。
具体来说,假设两个声明都有效,并且结果相同,例如在第一个 is_lt_comparable<int, void>
中,它如何知道要实例化哪个模板?
一个 static_assert
是一个 声明 并且没有定义任何实体。参见 cpprefrence article on Definitions and ODR。您的代码中只有模板实例化,但没有定义。
In the code above, why doesn't first and the last asserts trigger double definition of is_lt_comparable?
is_lt_comparable
不是类型。这是模板的名称。
is_lt_comparable_v<int>
导致 is_lt_comparable<int, void>
的扩展,这是一种类型。
is_lt_comparable_v<test>
导致 is_lt_comparable<test, void>
的扩展,这是一种不同的类型。
再一次,is_lt_comparable<std::vector<int>, void>
是另一种不同的类型。
你写:is_lt_comparable<int>
。事情是这样的。
选择主模板,并推断第二个模板参数,因为它是默认值。所以,你实际上有 is_lt_comparable<int, void>
.
现在考虑模板特化,看是否匹配。
它找到第一个(也是唯一的)特化,因为它是部分特化,而不是完整特化,所以基本上也需要实例化它。所以你得到:
is_lt_comparable<int, void_t<decltype(std::declval<int>() < std::declval<int>())>>
现在,如果 <
表达式格式错误,则不考虑特化,编译器回退到主模板。
但如果有效,则偏特化变为:is_lt_comparable<int, void>
。这与我们在 1) 中实例化的模板完全匹配,因此编译器选择了那个模板。正式地,这被称为部分排序规则。
如果您仍然感到困惑,请考虑一下:
template<typename> void foo() {}
template<> void foo<int>() {}
如果我这样做 foo<int>()
,也不会像您说的那样出现双重定义错误。特化比主模板更匹配,因此编译器甚至不使用 T = int
实例化主模板(它不能)。
让我们来具体一点:
#include <utility>
#include <vector>
template <typename ... Ts>
using void_t = void;
template <typename T, typename = void_t<>>
struct is_lt_comparable : std::false_type {};
template <typename T>
struct is_lt_comparable<T, void_t<decltype(std::declval<T>() < std::declval<T>())>> : std::true_type {};
template <typename T>
static constexpr bool is_lt_comparable_v = is_lt_comparable<T>::value;
struct test{};
int main()
{
#define error_message "detection doesn't work correctly"
static_assert(is_lt_comparable_v<int>, error_message);
static_assert(!is_lt_comparable_v<test>, error_message);
static_assert(is_lt_comparable_v<std::vector<int>>, error_message);
}
在上面的代码中,为什么 first 和 last 断言没有触发 is_lt_comparable
的双重定义?
void_t
带任何参数仍然是 void
。因此,模板的最后一个未命名参数始终是 void
。 IIRC 类型别名不被认为是不同的类型,因此我的直觉让我相信我遗漏了一些东西。
具体来说,假设两个声明都有效,并且结果相同,例如在第一个 is_lt_comparable<int, void>
中,它如何知道要实例化哪个模板?
一个 static_assert
是一个 声明 并且没有定义任何实体。参见 cpprefrence article on Definitions and ODR。您的代码中只有模板实例化,但没有定义。
In the code above, why doesn't first and the last asserts trigger double definition of is_lt_comparable?
is_lt_comparable
不是类型。这是模板的名称。
is_lt_comparable_v<int>
导致 is_lt_comparable<int, void>
的扩展,这是一种类型。
is_lt_comparable_v<test>
导致 is_lt_comparable<test, void>
的扩展,这是一种不同的类型。
再一次,is_lt_comparable<std::vector<int>, void>
是另一种不同的类型。
你写:is_lt_comparable<int>
。事情是这样的。
选择主模板,并推断第二个模板参数,因为它是默认值。所以,你实际上有
is_lt_comparable<int, void>
.现在考虑模板特化,看是否匹配。
它找到第一个(也是唯一的)特化,因为它是部分特化,而不是完整特化,所以基本上也需要实例化它。所以你得到:
is_lt_comparable<int, void_t<decltype(std::declval<int>() < std::declval<int>())>>
现在,如果
<
表达式格式错误,则不考虑特化,编译器回退到主模板。但如果有效,则偏特化变为:
is_lt_comparable<int, void>
。这与我们在 1) 中实例化的模板完全匹配,因此编译器选择了那个模板。正式地,这被称为部分排序规则。
如果您仍然感到困惑,请考虑一下:
template<typename> void foo() {}
template<> void foo<int>() {}
如果我这样做 foo<int>()
,也不会像您说的那样出现双重定义错误。特化比主模板更匹配,因此编译器甚至不使用 T = int
实例化主模板(它不能)。