Tiramisu
Tiramisu

Reputation: 43

Variadic arguments vs. upcasting arguments

The overloaded variadic version of a function is supposed to have the lowest priority, when the compiler chooses a template specialization. However, this is not the case when a function argument has to be upcasted to a base class first before it matches a template argument. See the example below.

class A {};
class B : public A {};
template<class... Args>
void foo(Args&&... argsPack) {
    std::cout << "variadic function called" << std::endl;
}
void foo(A&) {
    std::cout << "non-variadic function called" << std::endl;
}


int main() {
    foo("Hello");
    B b;
    foo(b);
}

The output of this program results in

variadic function called
variadic function called

If I wanted the non-variadic function to be called, I would have to delete the variadic overloaded function entirely. Or alternatively I would have to write a third overloaded function that accepts instances of class B directly:

void foo(B&) {
    std::cout << "non-variadic function called" << std::endl;
}

However, I want to avoid both solutions, because they are not elegant and error prone for my project. So is there a third solution for this?

Upvotes: 4

Views: 99

Answers (2)

Jarod42
Jarod42

Reputation: 218138

You might constraint your template. I add extra overload to simplify the requirement:

void foo() // Simulate empty pack
{
    std::cout << "variadic function called" << std::endl;
}

template <typename T, class... Args>
requires (!std::is_base_of_v<A, std::decay_t<T>> || sizeof...(Args) != 0)
void foo(T&& t, Args&&... argsPack) {
    std::cout << "variadic function called" << std::endl;
}
void foo(A&) {
    std::cout << "non-variadic function called" << std::endl;
}

Demo

Upvotes: 3

Fran&#231;ois Andrieux
Fran&#231;ois Andrieux

Reputation: 29032

You can achieve your goal by making both versions templates with universal references, and only enabling the non-variadic one if the argument derives from A.

Demo : https://godbolt.org/z/jqjvcbKfP

#include <iostream>
#include <type_traits>

class A {};
class B : public A {};

template<class... Args>
void foo(Args&&... argsPack) {
    std::cout << "variadic function called" << std::endl;
}

template<class T>
// Enables this version only if the condition is true
std::enable_if_t<std::is_base_of<A, std::remove_reference_t<T>>::value>
foo(T&& arg)
{
    std::cout << "non-variadic function called" << std::endl;
}


int main() {
    foo("Hello");
    B b;
    foo(b);
}

Upvotes: 3

Related Questions