Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Implicit conversions with variadic templates

Consider two function calls

foo({"a", 1}, {"b", "value"});
foo({"a", 1}, {"b", "value"}, {"c", 1.0});

Is there a way to write function foo for arbitrary number of argument pairs?

I was thinking something along the lines

template <typename... Args>
void foo(std::pair<const char*, Args>&&...);

which unfortunately does not work.

gcc fails with an error:

error: too many arguments to function 'void foo(std::pair<const char*, Args>&& ...) [with Args = {}]'
 foo({"aa", 1});
like image 471
Blaz Bratanic Avatar asked Dec 09 '16 20:12

Blaz Bratanic


3 Answers

Try to simplify a bit your example and consider this:

#include<utility>

template<typename T>
void foo(std::pair<char*, T>) {}

int main() {
    foo({"a", 1});
}

It doesn't compile, as you can see.
The problem is that { "a", 1 } is not a std::pair, even if you can construct one from it as it follows:

#include<utility>

void foo(std::pair<char*, int>) {}

int main() {
    foo({"a", 1});
}

The error is quite clear:

couldn't infer template argument 'T'

Why can't you?

Te compiler could construct such a pair once T is known. Anyway, T must be deduced and the compiler cannot do that because { "a", 1 } is not a pair from which it can be deduced.
Anyway, { "a", 1 } can be converted to a pair, in the specific case to a specialization of std::pair<char *, T>, but first of all T must be deduced.
Deduced from what? A pair, of course, but you don't have a pair yet.
And so on, in a loop.

Let's discuss now your attempt to do something similar that involves a variadic template: it goes without saying that, if even the simpler example shown above doesn't compile, its variadic extension (if any) would not compile as well for more or less the same reason.

Is there a way to write function foo for arbitrary number of argument pairs?

I would say no, unless you use pairs as arguments for foo.
It follows a minimal, working example:

#include<utility>

template <typename... Args>
void foo(std::pair<const char*, Args>&&...) {}

int main() {
    foo(std::make_pair("a", 1), std::make_pair("b", "value"));
}

If you prefer, you can also deduce the first argument, as long as its type is fixed:

#include<utility>

template <typename T, typename... Args>
void foo(std::pair<T, Args>&&...) {}

int main() {
    foo(std::make_pair("a", 1), std::make_pair("b", "value"));
}

Otherwise you can do this if it's not fixed:

#include<utility>

template <typename... First, typename... Second>
void foo(std::pair<First, Second>&&...) {}

int main() {
    foo(std::make_pair("a", 1), std::make_pair(0, "value"));
}
like image 123
skypjack Avatar answered Nov 07 '22 01:11

skypjack


Is there a way to write function foo for arbitrary number of argument pairs?

There are some solutions based on variadic templates but arguments must be pairs to allow compiler to deduce types. Then something like this might work:

template<typename... Args>
void foo() {}

template<typename T, typename U, typename... Args>
void foo(const std::pair<T, U>& p, Args... args) {
    std::cout << __PRETTY_FUNCTION__ << std::endl;
    foo(args...);
}

So for:

foo(std::make_pair("a", 1), std::make_pair("b", "value"), std::make_pair("c", 1.0));

The output (with clang 3.8) is:

void foo(const std::pair<T, U> &, Args...) [T = const char *, U = int, Args = <std::__1::pair<const char *, const char *>, std::__1::pair<const char *, double>>]
void foo(const std::pair<T, U> &, Args...) [T = const char *, U = const char *, Args = <std::__1::pair<const char *, double>>]
void foo(const std::pair<T, U> &, Args...) [T = const char *, U = double, Args = <>]

Here is the full working example.

like image 7
Edgar Rokjān Avatar answered Nov 07 '22 01:11

Edgar Rokjān


To expand a bit on Edgar Rokyan's answer, you can move the pair creation into the foo function:

template<typename... Args>
void foo() {}

// Forward declaration
template<typename U, typename... Args>
void foo(const char * str, U u, Args... args);

// When given a pair
template<typename U, typename... Args>
void foo(const std::pair<const char *, U>& p, Args... args) {
    std::cout << p.first << " = " << p.second << std::endl;
    foo(args...);
}

// when given a C string and something else, make a pair
template<typename U, typename... Args>
void foo(const char * str, U u, Args... args) {
    foo(std::make_pair(str, u), args...);
}

Then you can call it like:

foo("hi", 42,
    "yo", true,
    std::make_pair("Eh", 3.14),
    "foo", false,
    some_pair);
like image 4
Daniel Jour Avatar answered Nov 07 '22 00:11

Daniel Jour