2011-05-18 11 views
5

これは、1つのオブジェクトの私(剥奪)クラスとインスタンス化である:このコンストラクタのオーバーロードが間違って解決されるのはなぜですか?

template <typename T, typename Allocator = std::allocator<T> > 
class Carray { 
    typedef typename Allocator::size_type size_type; 

    // ... 

    explicit Carray(size_type n, const T& value, const Allocator& alloc = Allocator()) { 
     // ... 
    } 

    template<typename InputIterator> 
    Carray(InputIterator first, InputIterator last, const Allocator& alloc = Allocator()) { 
     // ... 
    } 

    // ... 
} 

Carray<int> array(5, 10); 

私は、これはCarray(size_type, const T&, const Allocator&)コンストラクタを呼び出すことを期待するが、それはしていません。 Apparantlyこれはtemplate<typename InputIterator> Carray(InputIterator, InputIterator, const Allocator&)に解決します。

この作品を意図したものにするために変更する必要はありますか? std::vector<int> v(5, 10)への電話が完全に正常に機能するので、私はそれも変だと思う。私はGCCの実装では、コンストラクタの定義を見れば、私は(私が__nのように、いくつかのコンパイラの実装名を改称)これを見つける:

template<typename T, typename A = std::allocator<T> > 
class vector { 
    typedef size_t size_type; 
    typedef T value_type; 
    typedef A allocator_type; 

    // ... 

    explicit vector(size_type n, const value_type& value = value_type(), const allocator_type& a = allocator_type()); 

    template<typename InputIterator> 
    vector(InputIterator first, InputIterator last, const allocator_type& a = allocator_type()); 

    // ... 
}; 

同じであると思われます。

答えて

2

これが(ポインタを含む)すべてのイテレータ型と現在の標準で動作するはずです。

#include <iostream> 
#include <iterator> 
#include <vector> 

// uses sfinae to determine if the passed in type is indeed an iterator 
template <typename T> 
struct is_iterator_impl 
{ 
    typedef char yes[1]; 
    typedef char no[2]; 

    template <typename C> 
    static yes& _test(typename C::iterator_category*); 

    template <typename> 
    static no& _test(...); 

    static const bool value = sizeof(_test<T>(0)) == sizeof(yes); 
}; 

template <typename T, bool check = is_iterator_impl<T>::value> 
struct is_iterator 
{ 
    typedef void type; 
}; 

template <typename T> 
struct is_iterator<T, false> 
{ 
}; 

template <typename T> 
struct is_iterator<T*, false> 
{ 
    typedef void type; 
}; 

template <typename T> 
struct foo 
{ 
    explicit foo(std::size_t n, const T& value) 
    { 
    std::cout << "foo:size_t" << std::endl; 
    } 

    template<typename InputIterator> 
    foo(InputIterator first, InputIterator last, typename is_iterator<InputIterator>::type* dummy = 0) 
    { 
    std::cout << "foo::iterator" << std::endl; 
    } 
}; 

int main(void) 
{ 
    // should not cause a problem 
    foo<int> f(1, 2); 

    // using iterators is okay 
    typedef std::vector<int> vec; 
    vec v; 
    foo<int> b(v.begin(), v.end()); 

    // using raw pointers - is okay 
    char bar[] = {'a', 'b', 'c'}; 
    foo<char> c(bar, bar + sizeof(bar)); 
} 

説明は、イテレータは通常、iterator_categoryなどいくつかのタイプを定義する必要があります。また、実際のイテレータを検出するために、これとsfinaeを利用することができます。複雑な点は、ポインタもイテレータであるが、これらの型は定義されていないことである(何かstd::iterator_traitsが特殊化を提供する)ので、渡された型がポインタの場合、デフォルトではイテレータ。このアプローチにより、積分型をテストする必要がなくなります。

参照のデモ:http://www.ideone.com/E9l1T

+0

ありがとうございます。これにより、私はboostまたはnon-C++ 03に依存せずにヘッダを完全に定義することができます。私はもちろんこれを生産コードでは使用しません(boost :: enable_ifは使いやすく適切です)。 – orlp

+0

@ nightcracker、心配するな...それは面白い挑戦だった... – Nim

7

明示的なコンストラクタには、size_tとintが必要です。あなたは2つのintを提供しています。

InputIteratorintを置き換えると、テンプレートがよりよく一致します。

標準のコンテナをもっと見ると、いくつかのテンプレートメタプログラミングを使用して、InputIteratorが実際のイテレータであるかどうか、または整数型であるかどうかを判断できます。これは、別の構成にリダイレクトされます。

template<class _InputIterator> 
    vector(_InputIterator _First, _InputIterator _Last, 
     const allocator_type& _Allocator = allocator_type()) 
    : _MyAllocator(_Allocator), _MyBuffer(nullptr), _MySize(0), _MyCapacity(0) 
    { _Construct(_First, _Last, typename std::is_integral<_InputIterator>::type()); } 

private: 
    template<class _IntegralT> 
    void _Construct(_IntegralT _Count, _IntegralT _Value, std::true_type /* is_integral */) 
    { _ConstructByCount(static_cast<size_type>(_Count), _Value); } 

    template<class _IteratorT> 
    void _Construct(_IteratorT _First, _IteratorT _Last, std::false_type /* !is_integral */) 
    { _Construct(_First, _Last, typename std::iterator_traits<_IteratorT>::iterator_category()); } 

コンパイラがのstd :: type_traitsを持っていない場合、あなたはまた、ブースト:: type_traitsを使用することができます。

編集
は、ここでそれを行うための一つの方法です。

+1

そして、どのように私はそれを解決するのでしょうか?また、 'vector'コンストラクタは' size_t'と 'int'も期待していますが、' int、int'を渡しても "正しいもの"に解決されます。 – orlp

+0

@nightcracker - 私の答えはまだ準備が整っていませんでした... –

+1

@ nightcracker:標準では:vectorはそのように振る舞います。これは、基本的なコア言語の動作の上に標準によって課される追加の要件です。あなたのクラスが同じように動作するようにしたい場合は、( 'std :: vector'のように)余分なステップを踏まなければなりません。 'std :: vector'の特定の実装を見て、そこでどのように行われているのか見ることができます。 – AnT

3

これを試してください。 2つのintが渡された場合、それは考慮からイテレータのコンストラクタを排除します:

template<typename InputIterator> 
Carray(InputIterator first, InputIterator last, 
    const Allocator& alloc = Allocator(), 
    typename boost::disable_if<boost::is_integral<InputIterator> >::type* dummy = 0) { 
} 

参考:http://www.boost.org/doc/libs/1_46_1/libs/utility/enable_if.html


はEDIT:ちょうどC++ 03 STLとどのような方法があります」への対応をし、ブーストなしで?

のstd :: type_traits場合、私は知らないがC++ 03かどうかである - 私は常に利用可能ブーストを持っているので、私はちょうどそれを使用しています。しかし、あなたはこれを試すことができます。これは、この特定のケースでは動作しますが、必要な一般性がないことがあります。第二のコンストラクタは、最初の2つの値があることを期待しながら、最初のコンストラクタは、「の値」引数は参照によって渡されることを期待し

template <class T> class NotInt { typedef void* type; }; 
template <> class NotInt<int> { }; 

template <typename T, typename Allocator = std::allocator<T> > 
class Carray { 
    ... 
    template<typename InputIterator> 
    Carray(InputIterator first, InputIterator last, 
     const Allocator& alloc = Allocator(), 
     typename NotInt<InputIterator>::type t = 0) { 
    std::cout << __PRETTY_FUNCTION__ << "\n"; 
    } 
}; 
+0

C++ 03 STLだけでブーストなしの方法はありますか?私はブーストを使用したくないわけではありませんが、私はこのヘッダーファイルを移植可能にし、可能であればブーストを避けたいと思います。 – orlp

+0

@ nightcracker:自分で書くことができます。 'is_integral'のような型の特性は、比較的簡単に指定できます。 – Puppy

0

を値渡し。私の経験上、C++はこの区別に関して非常に厳密です。オブジェクトコンストラクタの2番目の引数として整数値の代わりに整数変数を渡してみてください。

関連する問題