Search code examples
c++c++20autoc++-conceptsforwarding-reference

Why std::integral auto&& parameter does not work with an lvalue argument


I am not sure why the below code doesn't compile:

#include <array>
#include <algorithm>
#include <type_traits>
#include <concepts>
#include <cstdio>
#include <cwchar>


template <typename T>
concept Character = std::same_as< std::remove_cv_t<T>, char > ||
                    std::same_as< std::remove_cv_t<T>, signed char > ||
                    std::same_as< std::remove_cv_t<T>, unsigned char > ||
                    std::same_as< std::remove_cv_t<T>, wchar_t > ||
                    std::same_as< std::remove_cv_t<T>, char8_t > ||
                    std::same_as< std::remove_cv_t<T>, char16_t > ||
                    std::same_as< std::remove_cv_t<T>, char32_t >;

template <std::size_t N>
consteval auto create_character_array( std::integral auto&& fill_character ) noexcept
{
    std::array<std::remove_cvref_t<decltype( fill_character )>, N> dashes { };
    std::fill( std::begin( dashes ), std::end( dashes ), fill_character );

    return dashes;
}

int main( )
{
    const char ch { '^' };
    auto dashes { create_character_array<128>( ch ) };
    dashes.back( ) = '\0';

    std::fputs( std::data( dashes ), stdout );
}

I'm not sure but it seems that the universal reference (auto&&) and the concept std::integral don't work well for some reason. And replacing std::integral with my own more precise concept Character doesn't help either.

The following error is raised:

<source>: In function 'int main()':
<source>:30:46: error: no matching function for call to 'create_character_array<128>(const char&)'
   30 |     auto dashes { create_character_array<128>( ch ) };
      |                   ~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~
<source>:19:16: note: candidate: 'template<long unsigned int N, class auto:16>  requires  integral<auto:16> consteval auto create_character_array(auto:16&&)'
   19 | consteval auto create_character_array( std::integral auto&& fill_character ) noexcept
      |                ^~~~~~~~~~~~~~~~~~~~~~
<source>:19:16: note:   template argument deduction/substitution failed:
<source>:19:16: note: constraints not satisfied
In file included from /opt/compiler-explorer/gcc-trunk-20230509/include/c++/14.0.0/compare:37,
                 from /opt/compiler-explorer/gcc-trunk-20230509/include/c++/14.0.0/array:38,
                 from <source>:1:
/opt/compiler-explorer/gcc-trunk-20230509/include/c++/14.0.0/concepts: In substitution of 'template<long unsigned int N, class auto:16>  requires  integral<auto:16> consteval auto create_character_array(auto:16&&) [with long unsigned int N = 128; auto:16 = const char&]':
<source>:30:46:   required from here
/opt/compiler-explorer/gcc-trunk-20230509/include/c++/14.0.0/concepts:100:13:   required for the satisfaction of 'integral<auto:16>' [with auto:16 = const char&]
/opt/compiler-explorer/gcc-trunk-20230509/include/c++/14.0.0/concepts:100:24: note: the expression 'is_integral_v<_Tp> [with _Tp = const char&]' evaluated to 'false'
  100 |     concept integral = is_integral_v<_Tp>;
      |                        ^~~~~~~~~~~~~~~~~~
ASM generation compiler returned: 1
<source>: In function 'int main()':
<source>:30:46: error: no matching function for call to 'create_character_array<128>(const char&)'
   30 |     auto dashes { create_character_array<128>( ch ) };
      |                   ~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~
<source>:19:16: note: candidate: 'template<long unsigned int N, class auto:16>  requires  integral<auto:16> consteval auto create_character_array(auto:16&&)'
   19 | consteval auto create_character_array( std::integral auto&& fill_character ) noexcept
      |                ^~~~~~~~~~~~~~~~~~~~~~
<source>:19:16: note:   template argument deduction/substitution failed:
<source>:19:16: note: constraints not satisfied
In file included from /opt/compiler-explorer/gcc-trunk-20230509/include/c++/14.0.0/compare:37,
                 from /opt/compiler-explorer/gcc-trunk-20230509/include/c++/14.0.0/array:38,
                 from <source>:1:
/opt/compiler-explorer/gcc-trunk-20230509/include/c++/14.0.0/concepts: In substitution of 'template<long unsigned int N, class auto:16>  requires  integral<auto:16> consteval auto create_character_array(auto:16&&) [with long unsigned int N = 128; auto:16 = const char&]':
<source>:30:46:   required from here
/opt/compiler-explorer/gcc-trunk-20230509/include/c++/14.0.0/concepts:100:13:   required for the satisfaction of 'integral<auto:16>' [with auto:16 = const char&]
/opt/compiler-explorer/gcc-trunk-20230509/include/c++/14.0.0/concepts:100:24: note: the expression 'is_integral_v<_Tp> [with _Tp = const char&]' evaluated to 'false'
  100 |     concept integral = is_integral_v<_Tp>;
      |                        ^~~~~~~~~~~~~~~~~~

It compiles when '^' is passed as the argument. However ch makes it fail. This happened after adding std::integral to the function definition. Without it, the code compiles.

And in case this is not a good design, how should I define the above function to make it as versatile as possible? I want it to accept any value category.


Solution

  • The definition auto func(std::integral auto&& arg) is just shorthand for

    template <typename T>
    requires std::integral<T>
    auto func(T&& arg)
    

    The way forwarding references work is that when you call func, T will be deduced to either a value type if the argument is an rvalue or a reference type if the argument is an lvalue:

    func(42);       // T is int, so the type of arg is int&&
    func(some_int); // T is int&, so the type of arg is int& &&, which colapses to int&
    

    In the latter case, the std::integral<T> constraint is unsatisfied, since int& is not an integral type.


    To avoid this issue, either drop the reference (integral types are cheap to copy):

    template <std::size_t N>
    consteval auto create_character_array( std::integral auto fill_character ) noexcept
    

    Demo

    Or spell out the requirement long-form and explicitly add a std::remove_reference_t:

    template <std::size_t N, typename T>
    requires std::integral<std::remove_reference_t<T>>
    consteval auto create_character_array( T&& fill_character ) noexcept
    

    Demo