My problem:
Hi everyone,
I wrote some code that compiles, and I don't know why. Here is a minimal example:
#include <vector>
template <typename T>
concept IsIncrementable = requires(T a) { // Dummy concept for the example
++a;
};
template<typename T>
struct Test
{
static constexpr int a = [](auto x) -> int requires IsIncrementable<T> { return 0; }(0);
};
int main()
{
// T = std::vector<int>
// It does not satisfy IsIncrementable<T>
// Why does this code compiles ?
Test<std::vector<int>> t;
return 0;
}
Essentially, a variable is initialized through IIFE. Here are some indication about this code:
- Yes, the requires clause is on T, not on decltype(x).
- No, I DON'T want the requires clause to be on the Test class. Please, do not tell me I can do
template <IsIncrementable T> struct Test {};
instead or with requires. I know. And in the full example, I can not do such things... - It is important that the operator() of the lambda carries the requires clause, as in the more general context I can not directly template the lambda itself (other than with auto x).
- The variable x is here to allow for the requires clause to exist; otherwise the compiler may indicate that the requires clause shall only appear on templated code.
I thought the code might prevent Test to be instantiated with non-incrementable types. It turns out that I can do:
Test<std::vector<int>> t;
And I can use the a class, until I directly use the member variable, ie I use t.a
or Test<std::vector<int>>::a
.
Questions:
- Is this allowed by the standard not to check this requires clause?
- Or to only check this when the variable is used?
- If so, can you provide the section(s)?
- What is so special with the auto keyword here (see supplementary experiments)? I assume this have to do with the compiler deducing the type.
- Is there anything special about initialization with IIFE in this context?
- In fact, I am wondering as I am writing this post: Is it even legal to use IIFE in this context?
Thank you in advance.
Supplementary experiments:
- Using consteval / thread_local / volatile does not change the behavior.
- Introducing some runtime-only code in the lambda (specifically: return *(new int);) does not change the behavior either.
- Changing the requires clause to: requires IsIncrementable && false; or with any concept always evaluating to false, even if it involves
decltype(x)
does not change the behavior. - Using less-trivial type for a, eg. std::vector, std::string, does not change the behavior.
- G++ and Clang both have the same behavior.
- MSVC never allows for
Test<std::vector<int>>
- Removing the variable x: the code compiles. It means that surprisingly, I can write a non-templated function with a requires clause. Note that this appears to be a bug in gcc/clang, unless you answer false to question 7 (see below).
What DOES change the behavior however, is if I use auto instead:
static constexpr auto a = ...
makes it impossible to instantiate Test<std::vector<int>>
, which is, at least to me, the expected behavior.
My problem:
Hi everyone,
I wrote some code that compiles, and I don't know why. Here is a minimal example:
#include <vector>
template <typename T>
concept IsIncrementable = requires(T a) { // Dummy concept for the example
++a;
};
template<typename T>
struct Test
{
static constexpr int a = [](auto x) -> int requires IsIncrementable<T> { return 0; }(0);
};
int main()
{
// T = std::vector<int>
// It does not satisfy IsIncrementable<T>
// Why does this code compiles ?
Test<std::vector<int>> t;
return 0;
}
Essentially, a variable is initialized through IIFE. Here are some indication about this code:
- Yes, the requires clause is on T, not on decltype(x).
- No, I DON'T want the requires clause to be on the Test class. Please, do not tell me I can do
template <IsIncrementable T> struct Test {};
instead or with requires. I know. And in the full example, I can not do such things... - It is important that the operator() of the lambda carries the requires clause, as in the more general context I can not directly template the lambda itself (other than with auto x).
- The variable x is here to allow for the requires clause to exist; otherwise the compiler may indicate that the requires clause shall only appear on templated code.
I thought the code might prevent Test to be instantiated with non-incrementable types. It turns out that I can do:
Test<std::vector<int>> t;
And I can use the a class, until I directly use the member variable, ie I use t.a
or Test<std::vector<int>>::a
.
Questions:
- Is this allowed by the standard not to check this requires clause?
- Or to only check this when the variable is used?
- If so, can you provide the section(s)?
- What is so special with the auto keyword here (see supplementary experiments)? I assume this have to do with the compiler deducing the type.
- Is there anything special about initialization with IIFE in this context?
- In fact, I am wondering as I am writing this post: Is it even legal to use IIFE in this context?
Thank you in advance.
Supplementary experiments:
- Using consteval / thread_local / volatile does not change the behavior.
- Introducing some runtime-only code in the lambda (specifically: return *(new int);) does not change the behavior either.
- Changing the requires clause to: requires IsIncrementable && false; or with any concept always evaluating to false, even if it involves
decltype(x)
does not change the behavior. - Using less-trivial type for a, eg. std::vector, std::string, does not change the behavior.
- G++ and Clang both have the same behavior.
- MSVC never allows for
Test<std::vector<int>>
- Removing the variable x: the code compiles. It means that surprisingly, I can write a non-templated function with a requires clause. Note that this appears to be a bug in gcc/clang, unless you answer false to question 7 (see below).
What DOES change the behavior however, is if I use auto instead:
static constexpr auto a = ...
makes it impossible to instantiate Test<std::vector<int>>
, which is, at least to me, the expected behavior.
1 Answer
Reset to default 4Static data members are instantiated independently of the rest of the class. Your program isn't using Test<T>::a
anywhere, so it's simply not instantiated. If you did this:
int main()
{
return Test<std::vector<int>>::a;
}
then you'd get the error that you expected.
Additionally, when you did this:
What DOES change the behavior however, is if I use
auto
instead:static constexpr auto a = ...
Now analyzing a
requires evaluating the expression to determine its type, which leads to earlier instantiation. That just wasn't necessary when you explicitly specified int
, so it didn't happen yet.