0

私は可変個引数テンプレートと引数転送を実験してきました。一貫性のない動作を見つけたと思います。

説明のために、このプログラムは次のとおりです。

#include <iostream>
#include <typeinfo>
#include <tuple>
#include <cxxabi.h>

template <typename... Args> struct X {};

struct A {
    A () {}
    A (const A &) {
        std :: cout << "copy\n";
    }
};

template <typename T> const char * type_name () {
    return abi :: __cxa_demangle (typeid (T) .name (), 0, 0, NULL);
}

void foo () {}

template <typename... Args>
void foo (const A &, Args ... args) {
    std :: cout << type_name <X <Args...>> () << "\n“;    foo (args...);
}

int main () {
    foo (A(), A());
}

以下を出力します。

X<A, A>
copy
X<A>

fooのテンプレート特殊化にはconst参照の最初の引数がありますが、X<A>出力が示すように、テンプレート型は非参照型として推定されるため、可変引数は値によって渡されます。

だから、私はトーマスベッカーに相談します:

template<typename T>
void foo(T&&);

ここでは、以下が適用されます。

  1. タイプAの左辺値でfooが呼び出されると、TはA&に解決されるため、上記の参照折りたたみルールにより、引数タイプは事実上A&になります。

  2. タイプAの右辺値でfooが呼び出されると、TはAに解決されるため、引数タイプはA&&になります。

そしてこれを試してください:

template <typename... Args>
void foo (const A &, Args && ... args) {
    std :: cout << type_name<X<Args...>>() << "\n";
    foo (args...);
}

どの出力:

X<A, A>
X<A&>

今、私は困惑しています。ここでは、fooの呼び出しが3つあります。私の頭の中では、 (A()は名前のない右辺値であるため、参照であるため)、過負荷で解決するものをmain()推測する必要があります。これは順番に推論します。foo<A,A,A>(A&&,A&&,A&&)foo<A,A,A>(const A&,A&&,A&&)foo<A,A>(A&&,A&&)

質問は:どうしてX<A,A>非参照AX<A&>ありますが、参照があるのAですか?

std::forward再帰で使用できないため、これにより問題が発生します。

4

1 に答える 1

2

まず、間違ったメインを貼り付けたと仮定します。正しいメインは次のとおりです。

 int main () {
        foo (A(), A(), A());
 }

私が正しく推測したことを願っています。そうでなければ、この投稿の残りの部分は無効です:)
次に、私は標準的な用語があまり得意ではないので、以下があまり不正確でないことを願っています。

可変個引数テンプレートで何が起こるかを理解する最も簡単な方法は、コンパイラが効果的に実行することを自分で生成することだとよく感じます。

たとえば、最初の試み

void foo (const A &, Args ... args)

実際には、コンパイラによってこれらの3つの関数のようなものに解凍されます。

void foo3 (const A & a, A a1, A a2)
void foo2 (const A & a, A a0)
void foo1 (const A & a)

mainを呼び出すfoo3(A(), A(), A());と、コンパイラは最適化を行い、default-constructとcopyの代わりにdefault-constructa1とa2を実行します。ただし、foo3内でfoo2を呼び出すと、コンパイラーは最適化できなくなります。したがって、foo2(a1, a2)を呼び出すときは、foo2内のパラメーターa0をコピーして作成する必要があります。そして、それは私たちがトレースで見ることができるa0のコピー構築です(「コピー」)

ちなみに、なぜ最初の要素をconst refで渡し、残りを値で渡すのかわかりません。const-refですべてを渡してみませんか?

void foo (const A &, const Args& ... args)

では、rvalue-refを使った2回目の試行についてです。

void foo (const A &, Args && ... args)

foo3(A(), A(), A());メインで呼び出す場合、A()は右辺値であるため、このルールが適用されます。

タイプAの右辺値でfooが呼び出されると、TはAに解決されるため、引数タイプはA&&になります。

したがって、foo3は次のようになります。

void foo3 (const A &, A && a1, A && a2) {
    std :: cout << type_name<X<A, A>>() << "\n";
    foo2 (a1, a2);
}

ただし、ここでは注意してください。a1とa2はもう右辺値ではありません。それらには名前があるため、左辺値です。

したがって、foo2内では、このルールが適用されます。

タイプAの左辺値でfooが呼び出されると、TはA&に解決されるため、上記の参照折りたたみルールにより、引数タイプは事実上A&になります。

X<A&>したがって、foo2内のa0タイプは事実上A&になり、それがトレースで確認できる理由です。

完璧な転送を使用するには、この奇妙な仮定をあきらめる必要があると思います。

再帰でstd::forwardを使用できないため、これにより問題が発生します。

理由がわかりません。次のコードは正しいはずです:

void foo(A&&, Args&&... args)
{
   std::cout << type-name<X<Args...>> () << "\n";
   foo(std::forward<Args>(args...));
}

再帰のためにfoo()を呼び出すと、std :: forwardは各引数を左辺値から右辺値に再び変換するため、推論は正しくなります。

于 2011-06-17T13:00:59.327 に答える