Выводит знание оригинальных типов, одновременно отправляя

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

Я застрял, пытаясь "узнать" через дедукцию типы аргументов для данной функции, одновременно передавая их. Я думаю, что я могу упустить что-то решающее в том, как это работает

#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*&

Есть две вещи, которые я не понимаю:

  1. Почему ни один из напечатанных (через ошибку компоновщика) типов не соответствует тому, что я фактически передал? (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);
    

    но это "громоздко" по меньшей мере!

  2. В 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. Если динамическая типизация так важна для вас, почему вы не используете язык с динамической типизацией?

Другие вопросы по тегам