Skip to content

[clang-tidy] modernize-type-traits suggestion to use tuple_size_v is wrong inside constraints #233277

Open

Description

@nikola-benes

The expressions std::tuple_size_v<T> and std::tuple_size<T>::value are not equivalent when used inside constraints (such as the requires expression or the definition of a concept). This is because std::tuple_size_v<T> is not SFINAE-friendly (the variable is always declared for every T). Consider the following code:

template <typename T>
bool check() {
    if constexpr ( requires { std::tuple_size<T>::value; })
        return true;
    else
        return false;
}

Here, check<int>() returns false. Clang-tidy-23 suggests:

 warning: use c++17 style variable templates [modernize-type-traits]
      |     if constexpr ( requires { std::tuple_size<T>::value; })
      |                               ^                   ~~~~~~~
      |                                              _v

However, if we replace std::tuple_size<T>::value with std::tuple_size_v<T>, check<int>() returns true.

The situation is even worse when we actually try to use the tuple size, e.g., in concept definitions. Consider the code:

template <typename T>
concept is_pair = std::tuple_size<T>::value == 2;

template <typename T>
bool check() {
    return is_pair<T>;
}

Now, check<int>() again returns false. However, if we follow clang-tidy's suggestion and use std::tuple_size_v<T>, calling check<int>() does not compile:

error: implicit instantiation of undefined template 'std::tuple_size<int>'
      |     inline constexpr size_t tuple_size_v = tuple_size<_Tp>::value;
      |                                            ^
note: in instantiation of variable template specialization 'std::tuple_size_v<int>' requested here
      | concept is_pair = std::tuple_size_v<T> == 2;
      |                        ^
note: while substituting template arguments into constraint expression here
      | concept is_pair = std::tuple_size_v<T> == 2;
      |                   ^~~~~~~~~~~~~~~~~~~~~~~~~
note: while checking the satisfaction of concept 'is_pair<int>' requested here
      |         return is_pair<T>;
      |                ^~~~~~~~~~
note: in instantiation of function template specialization 'check<int>' requested here
      |         return check<int>();
      |                ^
note: template is declared here
      |     struct tuple_size;
      |            ^

Activity

  1. llvmorg-github-actions commented on Oct 18, 2026

    @llvmorg-github-actions

    @llvm/issue-subscribers-clang-tidy

    Author: Nikola Beneš (nikola-benes)

    Details The expressions `std::tuple_size_v<T>` and `std::tuple_size<T>::value` are not equivalent when used inside constraints (such as the `require` expression or the definition of a concept). This is because `std::tuple_size_v<T>` is not SFINAE-friendly (the variable is always declared for every T). Consider the following code:
    template &lt;typename T&gt;
    bool check() {
        if constexpr ( requires { std::tuple_size&lt;T&gt;::value; })
            return true;
        else
            return false;
    }

    Here, check&lt;int&gt;() returns false. Clang-tidy-23 suggests:

     warning: use c++17 style variable templates [modernize-type-traits]
          |     if constexpr ( requires { std::tuple_size&lt;T&gt;::value; })
          |                               ^                   ~~~~~~~
          |                                              _v
    

    However, if we replace std::tuple_size&lt;T&gt;::value with std::tuple_size_v&lt;T&gt;, check&lt;int&gt;() returns true.

    The situation is even worse when we actually try to use the tuple size, e.g., in concept definitions. Consider the code:

    template &lt;typename T&gt;
    concept is_pair = std::tuple_size&lt;T&gt;::value == 2;
    
    template &lt;typename T&gt;
    bool check() {
        return is_pair&lt;T&gt;;
    }

    Now, check&lt;int&gt; again returns false. However, if we follow clang-tidy's suggestion and use std::tuple_size_v&lt;T&gt;, calling check&lt;int&gt;() does not compile:

    error: implicit instantiation of undefined template 'std::tuple_size&lt;int&gt;'
          |     inline constexpr size_t tuple_size_v = tuple_size&lt;_Tp&gt;::value;
          |                                            ^
    note: in instantiation of variable template specialization 'std::tuple_size_v&lt;int&gt;' requested here
          | concept is_pair = std::tuple_size_v&lt;T&gt; == 2;
          |                        ^
    note: while substituting template arguments into constraint expression here
          | concept is_pair = std::tuple_size_v&lt;T&gt; == 2;
          |                   ^~~~~~~~~~~~~~~~~~~~~~~~~
    note: while checking the satisfaction of concept 'is_pair&lt;int&gt;' requested here
          |         return is_pair&lt;T&gt;;
          |                ^~~~~~~~~~
    note: in instantiation of function template specialization 'check&lt;int&gt;' requested here
          |         return check&lt;int&gt;();
          |                ^
    note: template is declared here
          |     struct tuple_size;
          |            ^
    

    It seems that the same issue affects all the _v variable shortcuts. (The type _t shortcuts seem to be fine, however.)

  2. changed the title [-][clang-tidy] modernize-type-traits suggestion to use `_v` is wrong inside constraints[/-] [+][clang-tidy] modernize-type-traits suggestion to use `tuple_size_v` is wrong inside constraints[/+] on Oct 18, 2026
  3. added a commit that references this issue on Oct 24, 2026
    4793391
  4. yashaggarwal85d commented on Oct 24, 2026

    @yashaggarwal85d
    Contributor

    I'd like to work on this. std::tuple_size<T>::value can be a substitution failure while std::tuple_size_v<T> is a hard error, so they only behave differently where a substitution failure matters. Of the traits the check handles, only tuple_size and variant_size have a primary template that isn't defined for every argument, so the other _v suggestions are still correct inside constraints. I'll send a PR that skips tuple_size/variant_size when the dependent ::value is inside a requires-expression, a concept definition or a requires-clause.

  5. added
    invalid-code-generationTool (e.g. clang-format) produced invalid code that no longer compiles
    false-positiveWarning fires when it should not
    on Oct 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    clang-tidyfalse-positiveWarning fires when it should notinvalid-code-generationTool (e.g. clang-format) produced invalid code that no longer compiles

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions