了解模板的重复评估 types/values
Understanding repeated evaluation of template types/values
我有以下代码,但我不明白为什么最后一个 !has_size<Bar>::value
只有在我没有在定义 [=14= 之前注释掉完全相同的 static_assert( !has_size<Bar>::value, ...)
时才计算为真]
#include <type_traits>
template <class, class = void> struct has_size : std::false_type {};
template <class T> struct has_size<
T, typename std::enable_if<(sizeof(T) > 0)>::type>
: std::true_type
{};
// expected success
struct Foo {};
static_assert( has_size<Foo>::value, "Foo doesn't have size");
struct Bar; // declare bar
// if we comment out this line, the static_assert below struct Bar{} fails
static_assert( !has_size<Bar>::value, "Bar has a size");
struct Bar {}; // define bar
// why is this true now, but false if I comment out the previous assertion?
static_assert( !has_size<Bar>::value, "Bar has a size");
我想稍后根据 has_size<Bar>
的值做出一些模板决策。该行为在 msvc、gcc 和 clang 中是相同的。我试图弄清楚这是否是有意为之且有据可查的行为,或者我是否依靠这种行为徘徊在 UB 土地或其他灾难中。想法?
C++ 中的每个完整类型 T
都有 sizeof(T)>0
或更简单地说 sizeof(T)
是一个有效的表达式,has_size
正在使用它来检测某个类型是否完整不是,并且正在通过 SFINAE.
做到这一点
第一个static_assert
struct Bar;
static_assert( !has_size<Bar>::value, "Bar has a size"); // (1)
导致 has_size<Bar>
的实例化 Bar
不完整,这导致 has_size
的第二个特化中的测试 sizeof(T) > 0
失败,此失败诉诸于使用满足 has_size<Bar>::value == false
.
的主模板 has_size : std::false_type
的定义
当第二个static_assert
struct Bar {};
static_assert( !has_size<Bar>::value, "Bar has a size"); // (2)
被评估,再次请求特化 has_size<Bar>
,但是这次 Bar
已经完成并且已经有 has_size<Bar>
的实例化(继承自 std::false_type
), 那个专门化被使用而不是实例化一个新的,因此仍然说 has_type<Bar>::value == false
.
当您评论第一个 static_assert
(1) 时,在计算 (2) 的那一刻,Bar
已经定义,现在 sizeof(T) > 0
有效且真实,select has_size<Bar> : std::true_type
的专业化,现在它满足 has_type<Bar>::value == true
.
不涉及UB。
为了拥有反映类型完整性变化的特征 T
,您可以同意:
template <class T>
constexpr auto has_size(int) -> decltype((void)sizeof(T), bool{})
{ return true; }
template <class T>
constexpr auto has_size(...) -> bool
{ return false; }
struct Bar;
static_assert( !has_size<Bar>(0), "Bar has a size");
struct Bar {}; // define bar
static_assert( !has_size<Bar>(0), "Bar has a size"); // fail
您可以将 class 模板实例化视为 "cached" 或 "memoized." 更正式地说,class 模板每个翻译单元有 a single point of instantiation。
所以当你写的时候:
struct Bar;
static_assert( !has_size<Bar>::value, "Bar has a size"); // #1
struct Bar {};
static_assert( !has_size<Bar>::value, "Bar has a size"); // #2
has_size<Bar>
在 #1
实例化。那是它的 only 实例化点。在 #2
,我们没有 "redo" 那个计算 - 所以它 仍然 是错误的。如果我们从不同的翻译单元再次这样做,以给出不同答案的方式,那将是格式错误的(不需要诊断),但在这种情况下 - 这是一个格式正确的程序。
当您注释掉 #1
时,现在 has_size<Bar>
的实例化点变为 #2
。在程序中的 那个 点,Bar
完成,所以 has_size<Bar>
现在是 true_type
... 所以静态断言触发。
我有以下代码,但我不明白为什么最后一个 !has_size<Bar>::value
只有在我没有在定义 [=14= 之前注释掉完全相同的 static_assert( !has_size<Bar>::value, ...)
时才计算为真]
#include <type_traits>
template <class, class = void> struct has_size : std::false_type {};
template <class T> struct has_size<
T, typename std::enable_if<(sizeof(T) > 0)>::type>
: std::true_type
{};
// expected success
struct Foo {};
static_assert( has_size<Foo>::value, "Foo doesn't have size");
struct Bar; // declare bar
// if we comment out this line, the static_assert below struct Bar{} fails
static_assert( !has_size<Bar>::value, "Bar has a size");
struct Bar {}; // define bar
// why is this true now, but false if I comment out the previous assertion?
static_assert( !has_size<Bar>::value, "Bar has a size");
我想稍后根据 has_size<Bar>
的值做出一些模板决策。该行为在 msvc、gcc 和 clang 中是相同的。我试图弄清楚这是否是有意为之且有据可查的行为,或者我是否依靠这种行为徘徊在 UB 土地或其他灾难中。想法?
C++ 中的每个完整类型 T
都有 sizeof(T)>0
或更简单地说 sizeof(T)
是一个有效的表达式,has_size
正在使用它来检测某个类型是否完整不是,并且正在通过 SFINAE.
第一个static_assert
struct Bar;
static_assert( !has_size<Bar>::value, "Bar has a size"); // (1)
导致 has_size<Bar>
的实例化 Bar
不完整,这导致 has_size
的第二个特化中的测试 sizeof(T) > 0
失败,此失败诉诸于使用满足 has_size<Bar>::value == false
.
has_size : std::false_type
的定义
当第二个static_assert
struct Bar {};
static_assert( !has_size<Bar>::value, "Bar has a size"); // (2)
被评估,再次请求特化 has_size<Bar>
,但是这次 Bar
已经完成并且已经有 has_size<Bar>
的实例化(继承自 std::false_type
), 那个专门化被使用而不是实例化一个新的,因此仍然说 has_type<Bar>::value == false
.
当您评论第一个 static_assert
(1) 时,在计算 (2) 的那一刻,Bar
已经定义,现在 sizeof(T) > 0
有效且真实,select has_size<Bar> : std::true_type
的专业化,现在它满足 has_type<Bar>::value == true
.
不涉及UB。
为了拥有反映类型完整性变化的特征 T
,您可以同意:
template <class T>
constexpr auto has_size(int) -> decltype((void)sizeof(T), bool{})
{ return true; }
template <class T>
constexpr auto has_size(...) -> bool
{ return false; }
struct Bar;
static_assert( !has_size<Bar>(0), "Bar has a size");
struct Bar {}; // define bar
static_assert( !has_size<Bar>(0), "Bar has a size"); // fail
您可以将 class 模板实例化视为 "cached" 或 "memoized." 更正式地说,class 模板每个翻译单元有 a single point of instantiation。
所以当你写的时候:
struct Bar;
static_assert( !has_size<Bar>::value, "Bar has a size"); // #1
struct Bar {};
static_assert( !has_size<Bar>::value, "Bar has a size"); // #2
has_size<Bar>
在 #1
实例化。那是它的 only 实例化点。在 #2
,我们没有 "redo" 那个计算 - 所以它 仍然 是错误的。如果我们从不同的翻译单元再次这样做,以给出不同答案的方式,那将是格式错误的(不需要诊断),但在这种情况下 - 这是一个格式正确的程序。
当您注释掉 #1
时,现在 has_size<Bar>
的实例化点变为 #2
。在程序中的 那个 点,Bar
完成,所以 has_size<Bar>
现在是 true_type
... 所以静态断言触发。