8

要約:最終的に、呼び出された正確な型を推測し、それらを転送するタプルを取得する関数を作成したいと思います(その型は、関数が呼び出された正確な型とは異なります)。

与えられた関数への引数のタイプを推測することで「知る」ことを試みながら、同時にそれらを転送しようとして立ち往生しています。私はこれがどのように機能するかについて重要な何かを見逃しているかもしれないと思います。

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

私が見たいのは、私の関数(egg1またはg2)が呼び出されたときに、元のタイプint,double,void*,std::string&,const char* 転送されたarugmentsの両方を認識して使用できるシナリオです。

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

ここで得られないことが2つあります。

  1. 印刷された(リンカーエラーを介した)タイプのいずれも、実際に渡したものと一致しないのはなぜですか?(int,double,void*,std::string&,const char)。私が実際に渡されたものを推測できますか?できれば「自然な」構文を使用します。つまり、すべてを1回だけ、明示的に何も書き出さないようにします。私は明示的に書くことができます:

    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&

4

2 に答える 2

2

最初の質問への回答:

関数への引数は式であり、ではありません。これら2つの違いは、第5章[expr]、p5で表現されています。

式のタイプが最初に「Tへの参照」(8.3.2、8.5.3)である場合、さらに分析する前にタイプがTに調整されます。

したがって、との間にはまったく違いはありませg(str)g(sref)g()常にstd::string参照を参照し、参照を確認しません。

さらに、式は左辺値または右辺値にすることができます(実際には、これはC ++ 11規則を単純化したものですが、3.10 [basic.lval]にある詳細が必要な場合は、この説明に十分近いです)。

2番目の質問への回答:

フォームのテンプレートパラメータ:

template <class T>
void g(T&&);

特別です。それらは、、、Tまたは次のようにT&さえ異なりconst T&&ます:

T&&左辺値にバインドする場合、はT左辺値参照型として推定されます。それ以外の場合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&&barbarAfoofoobar

しかし、foo(a)(左辺値A)を呼び出すTと、として推論されA&ます。キャストは次のようになります。

static_cast<A& &&>(t);

これは、参照の下で折りたたみルールを単純化して次のようにします。

static_cast<A&>(t);

つまり、左辺値が左辺値tにキャストされる(操作なしのキャスト)ため、bar左辺値を取るオーバーロードが呼び出されます。つまり、左辺値で呼び出す場合はfoo、左辺値でfoo呼び出しますbar。そして、それが完璧な転送という用語の由来です。

于 2011-10-25T15:42:40.210 に答える
0

タイプ(C ++でも)は、ほとんどがコンパイルタイプの概念です(もちろん、vtablesのRTTIを除く)。

完全に動的な型が必要な場合は、C++がそのための最適な言語ではない可能性があります。

プラグインまたはGCCMELT拡張機能(MELTはGCCを拡張するための高レベルのドメイン固有言語です)を使用してGCCを拡張する可能性があります(実際g++には、少なくとも4.6であると想定しています)。引数の型を定数文字列などでエンコードしますが、これにはある程度の作業が必要です(GCCに固有です)。

しかし、なぜCでこのようなバロック的なことをしたいのかわかりません。動的型付けが非常に重要な場合は、動的型付けされた言語を使用しませんか?

于 2011-10-28T20:23:32.653 に答える