Starl1ght
Starl1ght

Reputation: 4493

Variadic template chooses more common template instead of overload

Imagine this code:

#include <iostream>
void PrintInternal() {
    std::cout << std::endl;
}

template <typename T, typename...ARGS>
void PrintInternal(const T& head, const ARGS&...rest) {
    std::cout << head << " ";
    PrintInternal(rest...);
};

template <typename...ARGS>
void PrintInternal(const double& head, const ARGS&...rest) {
    std::cout << "DBL!!! " << head << " ";
    PrintInternal(rest...);
}

template <typename...ARGS>
void Print(const ARGS&...args) {
    PrintInternal(args...);
}

int main() {
    Print(1.1, 2, 3.3, 4);
    Print(0, 1.1, 2, 3.3, 4);
    return 0;
}

First Print outputs:

DBL!!! 1.1 2 3.3 4

My expectations were, that it would output DBL!!! before 3.3 or no DBL!!! at all. But why one???

Second Print outputs:

0 1.1 2 3.3 4

Why there is no DBL!!! output like at all, if we had one in first example.

And how to achieve, that for each double I will output something different without partial specialization? I've thought, that simple overloading should be ok...

Link to cpp.sh to view compilation results -> http://cpp.sh/42cz

Upvotes: 3

Views: 81

Answers (2)

Jarod42
Jarod42

Reputation: 217810

You have problem of visibility, you may fix it by forward declaration:

template <typename...ARGS> void PrintInternal(const double& head, const ARGS&...rest);

template <typename T, typename...ARGS>
void PrintInternal(const T& head, const ARGS&...rest) {
    std::cout << head << " ";
    PrintInternal(rest...);
}

template <typename...ARGS>
void PrintInternal(const double& head, const ARGS&...rest) {
    std::cout << "DBL!!! " << head << " ";
    PrintInternal(rest...);
}

Demo

Simpler would be to have the specific only for the simple print, and have the recursion apart, something like:

void printSingle(double d)
{
    std::cout << "DBL!!! " << d << " ";
}

template <typename T>
void printSingle(const T& t)
{
    std::cout << t << " ";
}

template <typename...ARGS>
void Print(const ARGS&...args) {
    const int dummy[] = {0, (printSingle(args), 0)...};
    static_cast<void>(dummy); // Avoid warning for unused variable
    std::cout << std::endl;
}

Demo

Upvotes: 0

Barry
Barry

Reputation: 303337

Lookup for PrintInternal() is going to find two types of functions:

  • All the functions visible at the point of definition of the function template.
  • All the functions in the associated namespaces of the dependent arguments of the function template.

In this case, all of our arguments are fundamental types, so there aren't any associated namespaces. That makes things easier. So when we start with:

#include <iostream>
void PrintInternal() { // #1
    std::cout << std::endl;
}

template <typename T, typename...ARGS>
void PrintInternal(const T& head, const ARGS&...rest) { // #2
    std::cout << head << " ";
    PrintInternal(rest...); // <== (*)
};

template <typename...ARGS>
void PrintInternal(const double& head, const ARGS&...rest) { // #3
    std::cout << "DBL!!! " << head << " ";
    PrintInternal(rest...);
}

That marked call to PrintInteral() only has two candidates: the nullary function (#1) and itself (#2). The other one, the more specialized PrintInteral() that takes a const double& (#3) isn't visible yet, so is never considered as a candidate. It's not that #2 was preferred to #3, it's just that it was the only option.

If you flip the ordering of the two overloads, then you'd have a different problem - you wouldn't be able to find #2!

This gives you a few options:

  1. Separate out printing an individual element from printing all of the elements. This way, you just have to overload PrintSingle(), which is easier to do.
  2. Forward-declare all your function templates so that they're all visible.
  3. Introduce another argument solely for the purpose of that second bullet point on top applying. Just a dummy argument that exists just to do name lookup with ADL. This solution is sometimes necessary, but always confusing:

    namespace N {
        struct adl { };
    
        void PrintInternal(adl ) {
            std::cout << std::endl;
        }
    
        template <typename T, typename...ARGS>
        void PrintInternal(adl, const T& head, const ARGS&...rest) {
            std::cout << head << " ";
            PrintInternal(adl{}, rest...);
        }
    
        template <typename...ARGS>
        void PrintInternal(adl, const double& head, const ARGS&...rest) {
            std::cout << "DBL!!! " << head << " ";
            PrintInternal(adl{}, rest...);
        }
    }
    
    template <typename...ARGS>
    void Print(const ARGS&...args) {
        PrintInternal(N::adl{}, args...);
    }
    

Upvotes: 6

Related Questions