Неявные преобразования с вариативными шаблонами

Рассмотрим два вызова функций

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

Есть ли способ записать функцию foo для произвольного количества пар аргументов?

Я что-то думал по строкам

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

который, к сожалению, не работает.

Ошибка gcc с ошибкой:

error: too many arguments to function 'void foo(std::pair<const char*, Args>&& ...) [with Args = {}]'
 foo({"aa", 1});

Ответы

Ответ 1

Чтобы развернуть бит на ответ Эдгара Рокьяна, вы можете переместить создание пары в функцию foo:

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...);
}

Затем вы можете вызвать его так:

foo("hi", 42,
    "yo", true,
    std::make_pair("Eh", 3.14),
    "foo", false,
    some_pair);

Ответ 2

Попробуйте немного упростить свой пример и подумайте над этим:

#include<utility>

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

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

Он не компилирует как вы можете видеть.
Проблема состоит в том, что { "a", 1 } не является std::pair, даже если вы можете построить один из них, как следует:

#include<utility>

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

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

Ошибка довольно ясна:

не удалось вывести шаблонный аргумент 'T'

Почему ты не можешь?

Te компилятор мог построить такую ​​пару раз, когда T известен. В любом случае, T должен быть выведен, и компилятор не может этого сделать, потому что { "a", 1 } не является парой, из которой он может быть выведен. Во всяком случае, { "a", 1 } можно преобразовать в пару, в конкретном случае, к специализации std::pair<char *, T>, но в первую очередь следует вывести T.
Из чего? Пара, конечно, но у вас еще нет пары.
И так далее, в цикле.

Давайте обсудим теперь вашу попытку сделать что-то подобное, что включает в себя вариационный шаблон: само собой разумеется, что если даже более простой пример, показанный выше, не компилируется, его вариационное расширение (если оно есть) не будет компилироваться также для более или по той же причине.

Есть ли способ написать функцию foo для произвольного количества пар аргументов?

Я бы сказал нет, если вы не используете пары в качестве аргументов для foo.
Это следует за минимальным рабочим примером:

#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"));
}

Если вы предпочитаете, вы также можете вывести первый аргумент, если его тип исправлен:

#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"));
}

В противном случае вы можете сделать это, если оно не исправлено:

#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"));
}

Ответ 3

Есть ли способ написать функцию foo для произвольного числа аргументов пары?

Есть несколько решений, основанных на вариативных шаблонах, но аргументы должны быть парами, чтобы позволить компилятору выводить типы. Тогда что-то вроде этого может работать:

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...);
}

Итак, для:

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

Выход (с clang 3.8):

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 = <>]

Здесь - полный рабочий пример.

Ответ 4

В С++ 17 вы можете обойти проблему и немного обмануть компилятор, используя шаблонный вывод конструкторов:

#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]