Search code examples
c++templatestype-inferencetype-traitstemplate-argument-deduction

Template argument deduction with multiple transformations


I'm trying to write a generic function with an argument that contains multiple transformations on the template type, such as:

#include <iostream>
#include <string>
#include <type_traits>

template< typename _T_ >
void foo
(
     const std::basic_string< typename std::remove_cv< typename std::remove_extent< _T_ >::type >::type > & str
)
{
    std::cout << str << std::endl;
}

int main( void )
{
    foo< char const [ 3 ] >( "abc" ); // OK
    foo( "abc" );                     // Cannot deduce template argument

    return 0;
}

Unfortunately, the compiler is unable to deduce the correct type.
Tested with latest versions of Clang, GCC and MSVC.

Interestingly, it seems the compiler is able to infer with one transformation:

 const std::basic_string< typename std::remove_extent< _T_ >::type > & str

Obviously, the example above fails, because of the const, hence the need for remove_cv after remove_extent.

Is this expected, and is there any way to achieve this?


Solution

  • Complicated names containing qualified-ids are non-deduced contexts in C++. In

    foo< char const [ 3 ] >( "abc" );
    

    you supply the template argument T. In

    foo( "abc" );
    

    the template argument T cannot be deduced (function arguments are separate from template arguments, so T will not be deduced from "abc").

    One solution is to deduce the template argument first and then construct the basic_string when the argument is a const CharT*:

    template <class CharT>
    void foo(const std::basic_string<CharT>& string)
    {
        // ...
    }
    
    template <class CharT>
    void foo(const CharT* p)
    {
        std::basic_string<CharT> s{p};
        foo(s);
    }
    

    Another solution is to simply rely on class template argument deduction to handle both cases:

    template <class Arg>
    void foo(Arg&& arg)
    {
        std::basic_string s{std::forward<Arg>(arg)};
        // ...
    }