Search code examples
c++templatesc++11variadic-templatestype-traits

Why is the "universal reference" overload being selected instead of the char array or char pointer?


I want to unpack a variadic template pack and select a particular overload based on each type in the pack.

I have 3 overloads for unpacking various types:

    // fixed size char arrays
    template<size_t N, typename... Ts>
    void unpack(const char (&)[N], Ts&&... ts);

    // char pointers
    template<typename... Ts>
    void unpack(const char*, Ts&&... ts);

    // all other types
    template<typename T, typename... Ts>
    void unpack(T&&, Ts&&... ts);

I'm passing a char array (char buf[1024]) as an lvalue to the variadic constructor. When unpacking the pack, the T&& overload is being selected.

How can I get the const char (&)[N] or const char* overload selected?

Example app:

#include <iostream>

struct foo
{
    template<typename... Ts>
    foo(Ts&&... ts)
    {
        unpack(std::forward<Ts>(ts)...);
    }

    template<size_t N, typename... Ts>
    void unpack(const char (&)[N], Ts&&... ts)
    {
        std::cout << "const T (&)[N]" << std::endl;
        unpack(std::forward<Ts>(ts)...);
    }

    template<typename... Ts>
    void unpack(const char*, Ts&&... ts)
    {
        std::cout << "const char*" << std::endl;
        unpack(std::forward<Ts>(ts)...);
    }

    template<typename T, typename... Ts>
    void unpack(T&&, Ts&&... ts)
    {
        std::cout << "T&&" << std::endl;
        unpack(std::forward<Ts>(ts)...);
    }

    void unpack()
    {
    }
};

int main()
{
    char buf[1024];
    const char* str = "foo";
    foo f(buf, str);
    return 0;
}

The above code results in the following being printed:

T&&
const char*

I would like it to print either:

const T (&)[N]
const char*

or

const char*
const char*

ie: select one of the two char overloads - array or pointer.


Solution

  • One possibility is removing the array overload and just use

    template<typename T, typename... Ts>
    std::enable_if_t<std::is_same<std::decay_t<T>, char>::value> unpack(T*, Ts&&... ts)
    {
        std::cout << "(const) char*" << std::endl;
        unpack(std::forward<Ts>(ts)...);
    }
    

    This is enabled only if T is deduced to be (possibly cv-qualified) char (meaning it's a perfect match for both char * and const char * as the first parameter), and in those cases it's more specialized than the base template, and hence selected by overload resolution.

    Replace enable_if_t and decay_t with the more verbose typename /*...*/::type if not using C++14.

    Demo.