Тип возвращаемой переменной в зависимости от пакета параметров sizeof…
-
21-12-2019 - |
Вопрос
Я хочу создать функцию, которая возвращает упакованный кортеж, если передается более одного аргумента шаблона, и неупакованное значение, если передается только один аргумент шаблона.
Например, я хотел бы foo<int>()
вернуть int
и foo<int, float>
вернуть что-то с типом std::tuple<int, float>
.
Все мои попытки добиться такого эффекта не увенчались успехом.
Рассмотрим следующий подход, используя структура типа типа:
template<typename... T>
struct return_type {
typedef std::tuple<T...> type;
};
template<>
struct return_type<int> {
typedef int type;
};
// ... insert partial specializations for other supported primitive types
template<typename... T>
auto foo() -> typename return_type<T...>::type {
if (sizeof...(T) == 1)
return zap<T...>(); // Returns something of type T, where T is the first parameter
else
return bar<T...>(); // Assume this returns a std::tuple<T...>
}
Это не удастся скомпилировать из-за разных типов возвращаемых значений в теле foo
.
Альтернативно, вот попытка использования decltype
:
<template T>
T singular();
<template... T>
std::tuple<T...> multiple();
template <typename... T>
auto foo() -> decltype(sizeof...(T) == 1 ? singular() : multiple())
{
... // as above
}
Это также не удастся скомпилировать, поскольку тернарный оператор ожидает, что обе ветви вернут один и тот же тип.
Наконец, наивный подход с использованием простой рекурсивной распаковки также терпит неудачу:
template<typename T>
T foo() { return T{}; // return something of type T }
template<typename... T>
std::tuple<T...> foo() { return bar<T...>(); // returns a tuple }
Это, конечно, не удается, поскольку компилятор не может определить, какую перегруженную функцию вызывать.
Я не понимаю, почему нечто подобное невозможно в C++11, поскольку вся информация, необходимая для определения типа возвращаемого значения, доступна во время компиляции.И все же я изо всех сил пытаюсь понять, какие инструменты позволят мне это сделать.Любая помощь и предложения будут оценены по достоинству.
Решение 2
Ух, я буквально нашел ответ после того, как наконец сделал перерыв и выпил (это тоже после пары часов попыток найти решение!).
Ответом является модификация последнего подхода:
template<typename T>
T foo() { return zap<T>(); /* returns a T */ }
template<typename T1, typename T2, typename... Ts>
std::tuple<T1, T2, Ts...> foo() { return bar<T1, T2, Ts...>(); /* returns a tuple */ }
Используя два фиктивных параметра, компилятор может однозначно решить, какую функцию следует вызвать.
Другие советы
Обычно я использую структуру для специализаций:
#include <iostream>
#include <tuple>
namespace Detail {
template <typename...Ts>
struct Foo {
typedef std::tuple<Ts...> return_type;
static return_type apply() { return return_type(); }
};
template <typename T>
struct Foo<T> {
typedef T return_type;
static return_type apply() { return return_type(); }
};
}
template <typename...Ts>
typename Detail::Foo<Ts...>::return_type foo() {
return Detail::Foo<Ts...>::apply();
}
int main ()
{
std::tuple<int, int> t = foo<int, int>();
int i = foo<int>();
}
Я бы использовал диспетчеризацию тегов.
template<class...Ts> struct many :std::true_type {};
template<class T>struct many :std::false_type {};
template<class...Ts> struct return_value {
typedef std::tuple< typename std::decay<Ts>::type... > type;
};
template<class T>struct return_value : std::decay<T> {};
template<typename T>
T singular( T&& t ) {
return std::forward<T>(t);
}
template<typename... Ts>
typename return_value<Ts...>::type multiple( Ts&&... ts ) {
return { std::forward<Ts>(ts)... };
}
template<typename...T>
typename return_value<T...>::type worker(std::false_type, T&&...t ) {
static_assert( sizeof...(T)==1, "this override is only valid with one element in the parameter pack" );
return singular(std::forward<T>(t)...);
}
template<typename...Ts>
typename return_value<Ts...>::type worker(std::true_type, Ts&&...t) {
return multiple(std::forward<Ts>(t)...);
}
template<class...Ts>
auto foo(Ts&&... t)
-> decltype(worker( many<Ts...>(), std::declval<Ts>()...) )
{
return worker(many<Ts...>(), std::forward<Ts>(t)...);
}
тогда я бы добавил идеальную переадресацию.
Мне легче рассуждать о перегрузках, чем о специализациях.