admin管理员组文章数量:1122832
The major C++ implementations seems to have quite a different idea about this. What to make of the validity of the following 4 assertions?
struct a {
template<typename T = void>
constexpr a() { (T&&)0; }
};
struct b : a {};
template<typename T = void>
constexpr int f() { return (T&&)0; }
template<typename T = void>
constexpr int v = (T&&)0;
static_assert(sizeof(a{})); // #1 - clang ok, gcc nope, msvc ok
static_assert(sizeof(b{})); // #2 - clang nope, gcc ok, msvc ok
static_assert(sizeof(f())); // #3 - clang ok, gcc nope, msvc ok
static_assert(sizeof(v<>)); // #4 - clang ok, gcc ok, msvc nope
Obviously, forming a reference to void (i.e. (T&&)0
where T = void
)
is not a valid expression. However, such an expression is never actually
evaluated in this example program, as it's only part of the
definition of an unevaluated operand. What really explains this
divergence in behavior amongst all of these compilers?
Demo
Clang's error message:
<source>:3:23: error: cannot form a reference to 'void'
3 | constexpr a() { (T&&)0; }
| ^
<source>:14:24: note: in instantiation of function template specialization
'a::a<void>' requested here
14 | static_assert(sizeof(b{}));
| ^
GCC's error message:
<source>: In instantiation of 'constexpr a::a() [with T = void]':
<source>:13:24: required from here
13 | static_assert(sizeof(a{}));
| ^
<source>:3:21: error: forming reference to void
3 | constexpr a() { (T&&)0; }
| ^~~~~~
<source>: In instantiation of 'constexpr int f() [with T = void]':
<source>:15:23: required from here
15 | static_assert(sizeof(f()));
| ~~^~~
<source>:8:28: error: forming reference to void
8 | constexpr int f() { return (T&&)0; }
| ^~~~~~
MSVC's error message:
<source>(11): error C7683: you cannot create a reference to 'void'
<source>(11): note: the template instantiation context (the oldest one first) is
<source>(16): note: see reference to variable template 'const int v<void>'
being compiled
The major C++ implementations seems to have quite a different idea about this. What to make of the validity of the following 4 assertions?
struct a {
template<typename T = void>
constexpr a() { (T&&)0; }
};
struct b : a {};
template<typename T = void>
constexpr int f() { return (T&&)0; }
template<typename T = void>
constexpr int v = (T&&)0;
static_assert(sizeof(a{})); // #1 - clang ok, gcc nope, msvc ok
static_assert(sizeof(b{})); // #2 - clang nope, gcc ok, msvc ok
static_assert(sizeof(f())); // #3 - clang ok, gcc nope, msvc ok
static_assert(sizeof(v<>)); // #4 - clang ok, gcc ok, msvc nope
Obviously, forming a reference to void (i.e. (T&&)0
where T = void
)
is not a valid expression. However, such an expression is never actually
evaluated in this example program, as it's only part of the
definition of an unevaluated operand. What really explains this
divergence in behavior amongst all of these compilers?
Demo
Clang's error message:
<source>:3:23: error: cannot form a reference to 'void'
3 | constexpr a() { (T&&)0; }
| ^
<source>:14:24: note: in instantiation of function template specialization
'a::a<void>' requested here
14 | static_assert(sizeof(b{}));
| ^
GCC's error message:
<source>: In instantiation of 'constexpr a::a() [with T = void]':
<source>:13:24: required from here
13 | static_assert(sizeof(a{}));
| ^
<source>:3:21: error: forming reference to void
3 | constexpr a() { (T&&)0; }
| ^~~~~~
<source>: In instantiation of 'constexpr int f() [with T = void]':
<source>:15:23: required from here
15 | static_assert(sizeof(f()));
| ~~^~~
<source>:8:28: error: forming reference to void
8 | constexpr int f() { return (T&&)0; }
| ^~~~~~
MSVC's error message:
<source>(11): error C7683: you cannot create a reference to 'void'
<source>(11): note: the template instantiation context (the oldest one first) is
<source>(16): note: see reference to variable template 'const int v<void>'
being compiled
Share
Improve this question
edited Nov 23, 2024 at 9:19
303
asked Nov 23, 2024 at 0:13
303303
4,6421 gold badge15 silver badges34 bronze badges
5
- Doesn't "3 compilers give 3 results" kind of guarantee that this is undefined behaviour? – Andras Deak -- Слава Україні Commented Nov 23, 2024 at 0:17
- 3 @AndrasDeak--СлаваУкраїні Not necessarily. Unspecified and implementation-defined behaviors are also possible. – jamesdlin Commented Nov 23, 2024 at 0:21
- 3 And so are compiler bugs. – Nate Eldredge Commented Nov 23, 2024 at 0:50
- 2 And so are bugs in the standard itself. – n. m. could be an AI Commented Nov 23, 2024 at 6:22
- 2 Sometimes the compilers aren't even necessarily "buggy", they just aren't yet caught up on a core issue resolution that was issued recently (or not so recently) – Brian Bi Commented Nov 23, 2024 at 15:49
1 Answer
Reset to default 2The a{}
, f()
, and v<>
examples are definitely well-formed under [temp.inst]/5 and [temp.inst]/6:
Unless a function template specialization is a declared specialization, the function template specialization is implicitly instantiated when the specialization is referenced in a context that requires a function definition to exist or if the existence of the definition affects the semantics of the program. [...]
Unless a variable template specialization is a declared specialization, the variable template specialization is implicitly instantiated when it is referenced in a context that requires a variable definition to exist or if the existence of the definition affects the semantics of the program. [...]
Consider this version of the program:
struct a {
template<typename T = void>
constexpr a();
};
struct b : a {};
template<typename T = void>
constexpr int f();
template<typename T = void>
extern const int v;
static_assert(sizeof(a{}));
static_assert(sizeof(b{}));
static_assert(sizeof(f()));
static_assert(sizeof(v<>));
template <typename T>
constexpr int v = (T&&)0;
int main() {}
a::a
is no longer defined; same for f
, and that makes GCC happy; it doesn't complain about a missing definition or anything, because asking for sizeof(a{})
or sizeof(f())
can be computed based on just the declarations. For the v
case, we aren't actually allowed to forward-declare it with constexpr
, but we can declare it const
and define it with constexpr
later. With this version, or with the declaration alone, MSVC stops complaining. So MSVC and GCC have bugs in this area where they instantiate definitions too eagerly.
The b
case is the most interesting. In order for the compiler to check whether sizeof(b{})
is well-formed, it has to check whether b
's default constructor is deleted. In Clang's case this appears to result in generating a "full definition" of b::b
(I use scare quotes because technically = default;
is already a definition—in the language of the standard, but not as we would typically think of it); in other words, it's as if the definition b() {}
were generated and since that function calls a::a
it means that a::a
must be instantiated. GCC is not as heavy-handed; it's able to figure out that b::b
is non-deleted, without actually going any further and forcing the instantiation of a::a
. However, the standard is not clear about which behavior is correct.
本文标签: cDo unevaluated template operands need to have valid defintionsStack Overflow
版权声明:本文标题:c++ - Do unevaluated template operands need to have valid defintions? - Stack Overflow 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.betaflare.com/web/1736300261a1930751.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论