Выводит знание оригинальных типов, одновременно отправляя
Резюме: я хочу закончить с функцией, которая выводит точные типы, с которыми она была вызвана, и принимает (например) кортеж, который передает их (типы которых будут отличаться от точных типов, с которыми была вызвана функция).
Я застрял, пытаясь "узнать" через дедукцию типы аргументов для данной функции, одновременно передавая их. Я думаю, что я могу упустить что-то решающее в том, как это работает
#include <tuple>
#include <string>
#include <functional>
template <typename ...Args>
struct unresolved_linker_to_print_the_type {
unresolved_linker_to_print_the_type();
};
void f(int,double,void*,std::string&,const char*) {
}
template <typename F, typename ...Args>
void g1(F func, Args&&... args) {
unresolved_linker_to_print_the_type<Args...>();
auto tuple = std::forward_as_tuple(args...);
unresolved_linker_to_print_the_type<decltype(tuple)>();
}
template <typename F, typename T, typename ...Args>
void g2(F func, const T& tuple, Args... args) {
unresolved_linker_to_print_the_type<Args...>();
unresolved_linker_to_print_the_type<decltype(tuple)>();
}
int main() {
int i;
double d;
void *ptr;
std::string str;
std::string& sref = str;
const char *cstr = "HI";
g1(f, i,d,ptr,sref,cstr);
g2(f, std::forward_as_tuple(i,d,ptr,sref,cstr), i,d,ptr,sref,cstr);
}
То, что я хотел бы видеть, это сценарий, когда моя функция (например, g1
или же g2
) вызывается, он знает и может использовать оба исходных типа - int,double,void*,std::string&,const char*
и переадресованные доводы тоже.
В этом случае я не могу найти эту информацию изнутри g1
или же g2
, Ошибка (преднамеренного, чтобы распечатать типы) показывает мне в g1
они есть:
int&, double&, void*&, std::string&, char const*&
int&, double&, void*&, std::string&, char const*&
И в g2
:
int, double, void*, std::string, char const*
int&, double&, void*&, std::string&, char const*&
Есть две вещи, которые я не понимаю:
Почему ни один из напечатанных (через ошибку компоновщика) типов не соответствует тому, что я фактически передал? (
int,double,void*,std::string&,const char
). Могу ли я сделать вывод, что я на самом деле был принят? Желательно с "естественным" синтаксисом, т.е. все только один раз и ничего явно не выписано. Я могу явно написать:g2<decltype(&f),decltype(std::forward_as_tuple(i,d,ptr,sref,cstr)),int,double,void*,std::string&,const char*>(f,std::forward_as_tuple(i,d,ptr,sref,cstr),i,d,ptr,sref,cstr);
но это "громоздко" по меньшей мере!
В
g1
Наличие&&
в объявлении подписи функции, кажется, изменить типы в параметре шаблонаArgs
сам. Сравните это с:template <typename T> void test(T t);
Или же:
template <typename T> void test(T& t);
используя любой из них с:
int i; test(i);
не меняет тип
T
, Почему&&
изменить типT
сам когда&
не делает?
2 ответа
Ответ на первый вопрос:
Аргументами функций являются выражения, а не типы. Разница между этими двумя выражена в главе 5 [expr], p5:
Если выражение изначально имеет тип "ссылка на T" (8.3.2, 8.5.3), тип корректируется до T перед любым дальнейшим анализом.
Таким образом, нет никакой разницы, что вообще когда-либо между g(str)
а также g(sref)
, g()
всегда видит std::string
и никогда не ссылаться.
Кроме того, выражения могут быть lvalue или rvalue (на самом деле это упрощение правил C++11, но оно достаточно близко для этого обсуждения - если вам нужны подробности, они приведены в 3.10 [basic.lval]).
Ответ на второй вопрос:
Параметры шаблона формы:
template <class T>
void g(T&&);
особенные. Они не похожи T
, T&
, или даже const T&&
следующим образом:
когда T&&
привязывается к lvalue, T
выводится как ссылочный тип lvalue, в противном случае T
выводит точно в соответствии с нормальными правилами удержания.
Примеры:
int i = 0;
g(i); // calls g<int&>(i)
g(0); // calls g<int>(0)
Это поведение для поддержки так называемой идеальной пересылки, которая обычно выглядит следующим образом:
struct A{};
void bar(const A&);
void bar(A&&);
template <class T>
void foo(T&& t)
{
bar(static_cast<T&&>(t)); // real code would use std::forward<T> here
}
Если один звонит foo(A())
(значение A
), T
выводит по нормальным правилам как A
, Внутри foo
мы снимали t
для A&&
(rvalue) и вызов bar
, Перегрузка bar
это занимает важное место A
затем выбирается. Т.е. если мы позвоним foo
со значением, то foo
звонки bar
со значением.
Но если мы позвоним foo(a)
(lvalue A
), затем T
выводит как A&
, Теперь актерский состав выглядит так:
static_cast<A& &&>(t);
который по правилам свертывания ссылок упрощается до:
static_cast<A&>(t);
Т.е. lvalue t
бросается в lvalue (не-бросок), и, таким образом, bar
перегрузка lvalue называется. Т.е. если мы позвоним foo
с lvalue, то foo
звонки bar
с lvalue. И вот откуда взялся термин идеальная пересылка.
Типы (даже в C++) в основном представляют собой понятие типа компиляции (за исключением, конечно, RTTI в vtables).
Если вам нужны полностью динамические типы, то C++ может оказаться не лучшим языком для этого.
Вы могли бы, возможно, расширить GCC (на самом деле g++
Предполагая, что это как минимум 4.6) с плагином или расширением GEL MELT (MELT - это высокоуровневый домен-специфичный язык для расширения GCC), который делает то, что вы хотите (например, предоставляя дополнительную встроенную функцию, кодирующую тип его аргументов в некоторая постоянная строка и т. д.), но это требует некоторой работы (и специфично для GCC).
Но я не понимаю, почему вы хотите делать такие вещи в стиле барокко на C. Если динамическая типизация так важна для вас, почему вы не используете язык с динамической типизацией?