6

私は非常に奇妙な問題を抱えています

このようなコードの場合

    template <typename T>
    struct A{
        explicit A(unsigned int size = 0, const T &t = T())
        {
        }
        template <typename InputIterator>
        A(InputIterator first, InputIterator last) {
            for(;first != last ; ++first)
            {
                *first; //do something with iterator
            }
        }
    };

たとえば、定義するとき

        A<int> a(10,10);

最初のコンストラクターの代わりに、イテレーターの 2 番目のコンストラクターが使用されます。同じように見えるとき、ベクトルコンストラクターはどのように機能しますか?

    explicit vector (size_type n, const value_type& val = value_type(),
             const allocator_type& alloc = allocator_type());

    template <class InputIterator>
     vector (InputIterator first, InputIterator last,
             const allocator_type& alloc = allocator_type());

そして、問題なくベクトル v(10,10) を作成できます。

PSこのようなエラーが発生しました

      temp.cpp: In instantiation of ‘A<T>::A(InputIterator, InputIterator) [with = int; T = int]’:
    temp.cpp:17:15:   required from here
    temp.cpp:12:4: error: invalid type argument of unary ‘*’ (have ‘int’)
4

5 に答える 5

4

yourの場合にコンパイラが 2 番目のコンストラクターを選択する理由A単純10です。これは、その符号なし整数型に変換する必要があることを意味します。その変換の必要性は、最初のコンストラクターが 2 番目のコンストラクターへのオーバーロードの解決を失うことになります (これは と完全に一致します)。この問題を回避するには、次のようにします。intsize_type10InputIterator = int

A<int> a(10u, 10);

これにより、変換の必要がなくなりint -> unsigned、最初のコンストラクターが「非テンプレートはテンプレートよりも優れている」句によってオーバーロードの解決に勝ちます。

一方、動作が異なる理由std::vectorは、言語仕様が標準シーケンスのコンストラクターに特別な扱いを与えているためです。引数として同じ型の 2 つの整数を使用したコンストラクターの呼び出しが、引用符から最初のコンストラクター (つまり、サイズと初期化コンストラクター) に「魔法のように」解決される必要があるだけです。std::vectorそれぞれの特定の実装がそれをどのように達成するかは、実装次第です。すべての整数型のコンストラクターをオーバーロードできます。と同様の機能を使用できますenable_if。コンパイラ自体にハードコードすることもできます。等々。

これは、たとえば C++03 での記述方法です。

23.1.1 シーケンス

9この条項と条項 21 で定義されたすべてのシーケンスについて:

— コンストラクタ

template <class InputIterator> 
X(InputIterator f, InputIterator l, const Allocator& a = Allocator()) 

以下と同じ効果があります。

X(static_cast<typename X::size_type>(f),
  static_cast<typename X::value_type>(l), a) 

InputIterator が整数型の場合

C++11 では、別の角度からアプローチすることでさらに進んでいますが、意図は同じままです。InputIterator入力反復子として資格がない場合は、テンプレート コンストラクターをオーバーロードの解決から除外する必要があると述べています。

したがって、クラス テンプレートAを同じように動作させたい場合std::vectorは、意図的にそのように設計する必要があります。プラットフォームの標準ライブラリの実装を実際に覗いて、std::vector.

とにかく、ローテクのブルート フォース ソリューションは、int引数に専用のオーバーロードされたコンストラクターを追加することです。

    explicit A(unsigned int size = 0, const T &t = T())
    { ... }

    explicit A(int size = 0, const T &t = T())
    { ... }

もちろん、これは最終的にすべての整数型にオーバーロードを追加する必要があることを意味する場合があります。

既に上で述べたより良い解決策は、または同様の SFINAE ベースの手法を使用して、整数引数のテンプレート コンストラクターを無効にすることです。enable_if例えば

    template <typename InputIterator>
    A(InputIterator first, InputIterator last, 
      typename std::enable_if<!std::is_integral<InputIterator>::value>::type* = 0)

コンパイラで利用できる C++11 機能はありますか?

于 2013-06-06T16:59:32.043 に答える
2

InputIteratorの場合int、インスタンス化された

A(int first, int last)

インスタンス化されたものよりも一致する

explicit A(unsigned int size = 0, const int &t = int())

最初の引数が であるためunsignedです。A<int> a((unsigned int)10,10)期待するコンストラクターを呼び出す必要があります。SFINAEを使用して、コンストラクターに 2 つの反復子が実際に渡されない限り、一致を防ぐこともできTます。

#include <iostream>

using namespace std;

template <typename T>
struct A{
    explicit A(unsigned int size = 0, const T &t = T())
    {
        cout << "size constructor for struct A" << endl;
    }

    template <class I>
    using is_T_iterator = typename enable_if<is_same<typename iterator_traits<I>::value_type, T>::value, T>::type;

    template <typename InputIterator>
    A(InputIterator first, InputIterator last,  is_T_iterator<InputIterator> = 0) {
        cout << "iterator constructor for struct A" << endl;
        for(;first != last ; ++first)
        {
            *first; //do something with iterator
        }
    }
};

int main()
{
    A<int>(10,10);

    A<int>((int*)0,(int*)0);

    //A<int>((char*)0,(char*)0); //<-- would cause compile time error since (char *) doesn't dereference to int

    return 0;
}

両方の引数が反復子であるという条件Tが厳しすぎる場合は、より緩い定式化があります。たとえば、両方の引数が反復子であることを保証できます。さらに進んで (ただし、上記の例ほどではありません)、変換可能な型をT( を使用してstd::is_convertible) 確実に「指す」ようにすることもできます。

于 2013-06-06T17:04:11.603 に答える
0

そうです、テンプレート化されたものの方が一致するので、それが選択されます。標準ライブラリの実装は、テンプレート化されたすべてのメンバーで賢明な動作を実現するのに苦労しています。独自の同様のコレクションを実装する場合は、特殊化の実装コードを検索することをお勧めします。

または、問題を回避する方法を見つけることができます。

関数オーバーロード選択のすべてのケースとそれと戦うためのいくつかのアドバイスを含む素晴らしい GOTW 記事がありました。

于 2013-06-06T17:01:46.767 に答える
0

の最初の引数は署名A<int>(10, 10)されているため、明示的なコンストラクターと一致しないため10、代わりにテンプレート化されたコンストラクターを使用しています。に変更するとA<int>(10u, 10)、期待どおりの結果が得られる場合があります。

于 2013-06-06T17:04:08.780 に答える
0

一般的なライブラリを作成している場合は、余分な作業を行い、テンプレート メタ プログラミングを使用してすべてのケースをキャッチすることをお勧めします。または、すべての整数型に明示的なオーバーロードを提供するだけです。一般的ではない用途では、通常、整数型にオーバーロードを提供するときはいつでも、 for も提供するという規則に従うだけで十分です(したがって、すでに提供しているものに加えintて、コンストラクターが必要になります)。A::A( int size, T const& initialValue = T() )

より一般的には、おそらくsizean を作成して、それで終了する必要intがあります。標準ライブラリは多くの歴史的な問題に巻き込まれており、size_tデフォルトで使用する必要がありますが、一般に、そうしない非常に強い理由がない限り、C++ の通常の整数型はint; です。さらに、C++ の unsigned 型には非常に奇妙なセマンティクスがあり、算術演算が発生する可能性がある場合はいつでも避ける必要があります。

于 2013-06-06T17:32:09.020 に答える