4

私には、C++ コンパイラの矛盾のように見えるものに遭遇しました。次のコード例では

#include <vector>
namespace test {
  class A : std::vector<int>
  {
    template<typename F>
    friend void bar(A const&a, F f) { for(auto i:a) f(i); }
    template<int K, typename F>
    friend void foo(A const&a, F f) { for(auto i:a) if(i&K) f(i); }
  };
}
int sum(test::A const&a)
{
  int s=0;
  foo<2>(a,[&s](int i) { s+=i; } );    // <-- error here
  bar   (a,[&s](int i) { s+=i; } );    // <-- but not here
  return s;
}

foogcc (4.7.0、std=c++11 を使用) は、「このスコープで宣言されていませんでした」(および代替案として提案) について不平を言いますが、次の行でtest::fooの使用法を喜んでコンパイルします。bar現在、fooとの両方が宣言barによって名前空間に挿入されているため、どちらも実際にはグローバル名前空間に存在する必要はありません。testfriend

Q1私は間違っていますか、それともこれは c++11 の新しいひねりですか、それとも gcc の動作がおかしいのでしょうか?

もちろん、using ディレクティブをグローバル名前空間に単純に挿入すれば、この問題は回避されます。しかし、Aテンプレートを作ると、

#include <vector>
namespace test {
  template<typename T>
  class A : std::vector<T>
  {
    template<typename F>
    friend void bar(A const&a, F f) { for(auto i:a) f(i); }
    template<int K, typename F>
    friend void foo(A const&a, F f) { for(auto i:a) if(i&K) f(i); }
  };
}
using test::foo;          // does not avoid compilation error
using test::bar;          // does not avoid compilation error
int sum(test::A<int> const&a)
{
  int s=0;
  foo<2>(a,[&s](int i) { s+=i; } );
  bar   (a,[&s](int i) { s+=i; } );
  return s;
}

gcc は再び文句を言います。(usingディレクティブなしで)「fooこのスコープで宣言されていません」(しかし、再び喜んでコンパイルしますbarが、示唆していませんtest::foo)または(usingディレクティブを使用して)「test::foo宣言されていません」(および同じtest::bar)のポイントでusing指令。

Q2これは、コンパイラ エラーのように見えます。usingディレクティブの有無に関係なく、 を呼び出すことはできませんtest::foo。それとも、C++ について見逃したことがあるのでしょうか。

最後に、次のようにフレンド定義をクラスの外に移動しようとしました

namespace test {
  template<typename T>
  class A : std::vector<int>
  {
    template<int K, typename F>
    friend void foo(A const&a, F f);
    template<typename F>
    friend void bar(A const&a, F f) { for(auto i:a) f(i); }
  };

  template<int K, typename T, typename F>
  void foo(A<T> const&a, F f) { for(auto i:a) if(i&K) f(i); }

}
using test::foo;

gcc が再び不平を言うと、今回はそれvoid test::foo(const test::A<T>&, F)が使用されているが定義されていないと主張しています... Q3何が問題なのですか?

サブ質問への回答は歓迎します。

4

2 に答える 2

3

Q1:

私は間違っていますか、それともこれはc ++ 11の新しいひねりですか、それともgccの誤動作ですか?

いいえ、これは正常な動作です。C++11規格のパラグラフ14.8.1/8から:

単純な関数名の場合、関数名が呼び出しのスコープ内に表示されていない場合でも、引数依存のルックアップ(3.4.2)が適用されます。これは、呼び出しがまだ関数呼び出し(3.4.1)の構文形式を持っているためです。ただし、明示的なテンプレート引数を持つ関数テンプレートが使用されている場合、呼び出しのポイントにその名前の関数テンプレートが表示されていない限り、呼び出しには正しい構文形式がありません。そのような名前が表示されない場合、呼び出しは構文的に整形式ではなく、引数に依存するルックアップは適用されません。そのような名前が表示されている場合は、引数依存のルックアップが適用され、他の名前空間に追加の関数テンプレートが見つかる場合があります。[ 例:

namespace A {
    struct B { };
    template<int X> void f(B);
}
namespace C {
    template<class T> void f(T t);
}
void g(A::B b) {
    f<3>(b); // ill-formed: not a function call
    A::f<3>(b); // well-formed
    C::f<3>(b); // ill-formed; argument dependent lookup
    // applies only to unqualified names
    using C::f;
    f<3>(b); // well-formed because C::f is visible; then
    // A::f is found by argument dependent lookup
}

—例を終了]


Q2:

ディレクティブを使用するかどうかに関係なく、test :: fooを呼び出すことはできないため、これはコンパイラエラーのように見えます。それとも、C ++について見逃したことがありますか?

クラスがインスタンス化されないクラステンプレートになった場合、コンパイラはインスタンス化時に発生する第2フェーズの名前ルックアップを実行しないため、2つの関数が宣言されてA<>いることを検出することはありません。friend

たとえば、宣言のにテンプレートの明示的なインスタンス化を導入した場合、状況が変化するのを確認する必要があります。using

template class test::A<int>;

または、の定義を変更して、2つの関数テンプレートAのみを宣言し、定義しないようにしてfriend、それらの関数テンプレートにクラス外の定義を提供することもできます。それは、あなたが実際にやろうとしたことだと思います。だが...

Q3:

gccは再び文句を言いますが、今回はvoid test :: foo(const test :: A&、F)が使用されていますが、定義されていないと主張しています...では、何が問題なのですか?

問題は、後で定義するのと同じ関数をフレンドとして宣言していないことです。定義した関数が1つの追加の引数(T)をとることに注意してください。宣言を修正すると、プログラムがコンパイルされます。

namespace test 
{
    template<typename T>
    class A : std::vector<int>
    {
        template<int K, typename C, typename F>
        //              ^^^^^^^^^^  (can't use T here, it would shadow
        //                           the class's template parameter)
        friend void foo(A<C> const&a, F f);
    };

    template<int K, typename C, typename F>
    void foo(A<C> const&a, F f) 
    { for(auto i:a) if(i&K) f(i); }
}

using test::foo; // Just don't remove this, or we will be back in Q1 ;-)

結論:

したがって、必要なすべての変更を行った後、プログラムは次のようになります。

#include <vector>

namespace test
{
    template<typename T>
    class A : std::vector<T>
    {
        template<typename F, typename C>
        friend void bar(A<C> const&a, F f);

        template<int K, typename F, typename C>
        friend void foo(A<C> const&a, F f);
    };

    template<typename F, typename C>
    void bar(A<C> const&a, F f) { for(auto i:a) f(i); }

    template<int K, typename F, typename C>
    void foo(A<C> const&a, F f) { for(auto i:a) if(i&K) f(i); }
}

using test::foo;
using test::bar;

int sum(test::A<int> const& a)
{
    int s=0;
    foo<2>(a,[&s](int i) { s+=i; } );
    bar   (a,[&s](int i) { s+=i; } );

    return s;
}
于 2013-02-28T16:37:28.537 に答える
1

あなたの問題とあなたの質問への答えは ADL と呼ばれ、それがいつ適用されるかの規則です。これは C++11 では新しいものではなく、GCC の問題ではありません。

Q1:a型のパラメーターがあるtest::Aため (最初の例)、ADL (引数依存ルックアップ) は名前空間内のメソッドを検索しますtestが、テンプレート以外の呼び出しのみを検索します。foo<2>これが、 (テンプレート呼び出し) が見つからない理由barです。

Q2: Q3 の後に回答されました。以下を参照してください。

Q3:の関数定義はtest::foo、 のフレンドとして宣言した関数を定義していませんtest::A<T>。に変更します

namespace test
{
  template<typename T>
  class A;

  template<int K, typename F,typename T>
  void foo(A<T> const&a, F f);

  template<typename T>
  class A : std::vector<int>
  {
    template<int K, typename F,typename U>
    friend void foo(A<U> const&a, F f);
    template<typename F>
    friend void bar(A const&a, F f) { for(auto i:a) f(i); }
  };

  template<int K, typename F,typename T>
  void foo(A<T> const&a, F f) { for(auto i:a) if(i&K) f(i); }
}
using test::foo;

Q2: Q3 と同様に、次のように修正できます。

#include <vector>
namespace test {
  template<typename T>
  class A;

  template<typename F,typename T>
  void bar(A<T> const&a, F f);
  template<int K, typename F,typename T>
  void foo(A<T> const&a, F f);

  template<typename T>
  class A : std::vector<T>
  {
    template<typename F,typename U>
    friend void bar(A<U> const&a, F f);
    template<int K, typename F,typename U>
    friend void foo(A<U> const&a, F f);
  };

  template<typename F,typename U>
  void bar(A<U> const&a, F f) { for(auto i:a) f(i); }
  template<int K, typename F,typename U>
  void foo(A<U> const&a, F f) { for(auto i:a) if(i&K) f(i); }
}
using test::foo;
using test::bar;
int sum(test::A<int> const&a)
{
  int s=0;
  foo<2>(a,[&s](int i) { s+=i; } );
  bar   (a,[&s](int i) { s+=i; } );
  return s;
}

Andy は、元の例が機能しない理由を既に説明しました。

于 2013-02-28T16:21:27.630 に答える