Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What pattern matching (if any) is applied for C++ variadic template function invocation?

Tags:

c++

templates

I just naïvely wrote this:

#include <stdio.h>

template< class... Args >
auto format_for( Args... args, int last_arg )
    -> char const*
{
    // using Specifier = char const [3];
    // static Specifier const s[] = { {'%', 'f'+(0*args), ' '}..., {'%', 'f', 0} };
    return reinterpret_cast<char const*>( "" );
}

auto main() -> int
{
    printf( "'%s'\n", format_for( 5, 2, 1 ) );
}

It crashes Visual C++ 2015 update 1, an ICE (Internal Compiler Error), and g++ 5.1.0 maintains that the function only takes one argument, presumably as a result of ignoring the Args after failing to match this template parameter:

C:\my\forums\so\081> cl printf.cpp /Feb
printf.cpp
printf.cpp(14): fatal error C1001: An internal error has occurred in the compiler.
(compiler file 'f:\dd\vctools\compiler\cxxfe\sl\p1\cxx\dymto.c', line 6771)
 To work around this problem, try simplifying or changing the program near the locations listed above.
Please choose the Technical Support command on the Visual C++
 Help menu, or open the Technical Support help file for more information

C:\my\forums\so\081> g++ printf.cpp
printf.cpp: In function 'int main()':
printf.cpp:14:43: error: no matching function for call to 'format_for(int, int, int)'
     printf( "'%s'\n", format_for( 5, 2, 1 ) );
                                           ^
printf.cpp:4:6: note: candidate: template<class ... Args> const char* format_for(Args ..., int)
 auto format_for( Args... args, int last_arg )
      ^
printf.cpp:4:6: note:   template argument deduction/substitution failed:
printf.cpp:14:43: note:   candidate expects 1 argument, 3 provided
     printf( "'%s'\n", format_for( 5, 2, 1 ) );
                                           ^

C:\my\forums\so\081> _

So,

  • Why does the above not compile with g++?

  • How can the intent (hopefully evident from the code) be expressed?

  • More generally, what are the rules for matching a call to declaration of variadic template function?

like image 527
Cheers and hth. - Alf Avatar asked Jan 28 '16 18:01

Cheers and hth. - Alf


1 Answers

Args appears in a non-deduced context. In this case, that makes it being deduced as the the empty pack.

If you want to extract the last argument but maintain usual deduction rules, you can write a simple helper:

template <typename U>
constexpr U&& last(U&& u) {return std::forward<U>(u);}
template <typename U, typename... T>
constexpr decltype(auto) last(U&&, T&&... t) {return last(std::forward<T>(t)...);}

Demo.

More generally, what are the rules for matching a call to declaration of variadic template function?

Those are pretty verbose, but function parameter packs that are not trailing will generally either be deduced to empty ones or yield a deduction failure.


In your particular case, try index_sequences:

template <class... Args, std::size_t... indices>
auto format_for( std::index_sequence<indices...>, Args... args )
{
    auto tup = std::forward_as_tuple(std::forward<Args>(args)...);
    using Specifier = char const [3];
    static Specifier const s[] = { {'%', (char)('f'+(0*std::get<indices>(tup))), ' '}..., {'%', 'f', 0} };
    int last_arg = std::get<sizeof...(Args)-1>(tup);
    return s;
}

template <class... Args>
auto format_for( Args&&... args ) {
    return format_for(std::make_index_sequence<sizeof...(Args)-1>{}, 
                      std::forward<Args>(args)...);
}

…and hope that the compiler optimizes well - Demo 2. Or go down the cheeky road:

template <class... Args, std::size_t... indices>
auto format_for( std::index_sequence<indices...>, Args... args )
{
    using Specifier = char const [3];
    static Specifier const s[] = {
      {'%', (char)(indices == sizeof...(Args)-1?
       'f' : 'f'+(0*args)), ' '}... };
    int last_arg = last(args...); // As before
    return s;
}

template <class... Args>
auto format_for( Args&&... args ) {
    return format_for(std::make_index_sequence<sizeof...(Args)>{}, 
                      std::forward<Args>(args)...);
}

Demo 3.

like image 148
Columbo Avatar answered Oct 13 '22 06:10

Columbo