14

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});
Guillaume Racicot
  • 39,621
  • 9
  • 77
  • 141
Blaz Bratanic
  • 2,279
  • 12
  • 17

4 Answers4

8

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"));
}
skypjack
  • 49,335
  • 19
  • 95
  • 187
7

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.

Edgar Rokjān
  • 17,245
  • 4
  • 40
  • 67
  • I have tested your code with clang 3.8, it does not compile. Whatsoever your code does not conform to the standard. – Oliv Dec 10 '16 at 08:56
  • 1
    @Oliv I added the link to the answer. Now you can check the full code. Which part of the code does not conform to the standard? – Edgar Rokjān Dec 10 '16 at 09:01
  • 1
    @EdgarRokyan `__PRETTY_FUNCTION__`? – skypjack Dec 10 '16 at 09:33
  • @skypjack hah, yeah, this is an extension, of course) – Edgar Rokjān Dec 10 '16 at 09:38
  • @EdgarRokyan Moreover, you don't have to get the pairs one at the time out of the pack. See the last snippet from [this answer](http://stackoverflow.com/a/41069242/4987285). It is somehow similar to what the OP posted in the question.I think he's interested in the `Args`. – skypjack Dec 10 '16 at 09:50
  • @skypjack I've already checked it and your solution is nice! Can we modify it somehow to avoid hardcoding the first type in these pairs as `const char*`? – Edgar Rokjān Dec 10 '16 at 09:54
  • @EdgarRokyan Updated my answer. Was that what you were asking for? See the last snippet. – skypjack Dec 10 '16 at 11:24
  • @EdgarRokyan Added another snippet, in case the first parameter is not of a fixed type. ;-) – skypjack Dec 10 '16 at 13:40
  • @EdgarRokyan I did not see the call with make_pair. – Oliv Dec 11 '16 at 13:58
4

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);
Community
  • 1
  • 1
Daniel Jour
  • 15,896
  • 2
  • 36
  • 63
  • Please remove the `template` above the first overload declaration of `foo` (remove the first line). You do not call any foo(), you just call foo(). Declaring the first empty foo overload as a template just bring confusion: it looks like there were a confusion between template specialization and overloading. In your code you declare three overload foo, but only one specialization of the first overload is used: foo<>(), so the first overload do not need to be a template. – Oliv Dec 11 '16 at 22:53
  • Correction: "only one instantiation of the first overload is used:foo<>()" – Oliv Dec 11 '16 at 23:02
0

In c++17 you may be able to workaround the problem and cheat compiler a bit by using template deduction of constructors:

#include <iostream>
#include <utility>

template <class... Args>
struct P:std::pair<Args...> {
    P(Args... args):std::pair<Args...>(args...) { }
};

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

int main() {
    foo(P{"abc", 1}, P{"abc", "abc"}, P{"abc", 2.0});
}

[live demo]

W.F.
  • 13,888
  • 2
  • 34
  • 81
  • 1
    If you can use deduction from constructors - you can write `foo(std::pair{"a", 1}/*...*/)`. If you don't want to write such a "long" name as `std::pair` - write `template using P = std::pair`. No need for this `struct P` stuff. – Rostislav Dec 10 '16 at 16:55
  • @Rostislav well maybe in c++17 it will work for now gcc does not allow it: [example](http://melpon.org/wandbox/permlink/bVkVFq0kXpcbN1Bn) – W.F. Dec 10 '16 at 16:59
  • That's quite interesting as to why this extra layer works fine for string literals, but direct `pair` construction doesn't (it seems to work fine for non-string-literals). – Rostislav Dec 10 '16 at 19:07
  • @Rostislav I wouldn't blame gcc as it does not fully support c++17 yet :) – W.F. Dec 10 '16 at 21:06
  • Sure thing - it's just amazing how complex compilers are and which tricks can get some things to work :) – Rostislav Dec 10 '16 at 23:13
  • 1
    @Rostislav Class template argument deduction doesn't work with alias templates. – Oktalist Dec 10 '16 at 23:14