最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

c++ - Static constexpr data member with templated IIFE does not check requires-clause - Stack Overflow

programmeradmin0浏览0评论

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:

  1. Yes, the requires clause is on T, not on decltype(x).
  2. 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...
  3. 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).
  4. 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:

  1. Is this allowed by the standard not to check this requires clause?
  2. Or to only check this when the variable is used?
  3. If so, can you provide the section(s)?
  4. What is so special with the auto keyword here (see supplementary experiments)? I assume this have to do with the compiler deducing the type.
  5. Is there anything special about initialization with IIFE in this context?
  6. 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:

  1. Yes, the requires clause is on T, not on decltype(x).
  2. 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...
  3. 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).
  4. 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:

  1. Is this allowed by the standard not to check this requires clause?
  2. Or to only check this when the variable is used?
  3. If so, can you provide the section(s)?
  4. What is so special with the auto keyword here (see supplementary experiments)? I assume this have to do with the compiler deducing the type.
  5. Is there anything special about initialization with IIFE in this context?
  6. 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.

Share Improve this question asked Feb 5 at 16:05 user25512387user25512387 232 bronze badges
Add a comment  | 

1 Answer 1

Reset to default 4

Static 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.

发布评论

评论列表(0)

  1. 暂无评论