2017-12-31 267 views
2

コレクションから標準出力範囲を出力する汎用関数を記述したいとします。それは普遍的になっているので、私は仮定テンプレート引数の型をアサートする方法STLイテレータの型

std::vector<std::string> names = { "John", "Henry", "Mark" }; 

と同様に...その:

std::vector<int> years = { 100, 200, 400 }; 

...プリントアウトすることが可能となります。コレクションの種類以来

は異なる場合があり、そして私がテンプレート関数を使用し、基本クラスのイテレータを渡すためにチャンスを与えSTLコレクションの基本クラスが存在しない:すべては今うまく機能

template<typename TIterator> 
void PrintRange(TIterator beginIter,TIterator endIter) 
{   
    for(auto it = beginIter; it != endIter; ++it) 
    { 
     std::cout << *it << std::endl; 
    } 
} 

、今私は書くことができます:

PrintRange(names.begin(), names.end()); 

と:

PrintRange(years.begin(), years.end()); 

しかし、今、私は彼にしたいです私の機能のlpクライアントは、なぜ彼がそれを使用するときにエラーがあるかをより速く理解することができます。今、私が呼び出したとき:

main.cpp:23:34: error: invalid type argument of unary ‘*’ (have ‘int’)

私のようなものを印刷したいと思います:

PrintRange(100, 400); 

エラーがあり

One of arguments does not correspond to expected argument of type 'iterator'

だから、この問題へのどのようなアプローチが最善です:

  1. 気にすることは重要ではありませんエラーメッセージは のように意味がありません。ユーザーはテンプレートクラスコードを に分析して、間違いの理由を確立する必要があります。

  2. static_assertを使用すると、すべての可能性をアサーションすることができますが、基本クラスがないため、関数の引数がANYイテレータであることをどのようにアサートするのですか?

static_assert(std::is_base_of::iterator >::value);

これは文字列のみイテレータのベクトルを主張するだろう...

+0

なぜさまざまなタイプの開始と終了のイテレータですか?それらは同じものでなければなりません – Fureeish

+0

関数に適切な文書を追加すると、ユーザーはテンプレートコードを調べる必要がなく、入力の検証に何も追加作業をする必要はありません。コンパイラの検証をさせてください! –

答えて

2

個人的に、私はあなたが追加のエラー・メッセージについてはあまり気にしない場合がありますので、あなたの最初のアプローチは、完全に罰金だと思います。

一方、意味のあるメッセージを印刷する場合は、hereと記載されているイテレータを検出するカスタムタイプの特性を実装してから、static_assertで使用します。だから、コードのようなものに変身:

template<typename TIterator> 
void PrintRange(TIterator beginIter, TIterator endIter) 
{   
    static_assert(is_iterator<TIterator>::value, 
     "TIterator is not an iterator type"); 

    for(auto it = beginIter; it != endIter; ++it) 
    { 
     std::cout << *it << std::endl; 
    } 
} 
0

エドガーRokyanが提供する回答は非常に便利ですが、私は(私たちはより多くのコードを実装する必要があるため、おそらく悪い方、)別の解決策を知っています。

このソリューションは、イテレータの型チェックではなく、方向性のあるヒントです。PrintRange関数を与えると、TIterator - operator*operator++、およびoperator !=に対して3つの演算子を定義する必要があると仮定します。

は、あなたがこれを使用することができ、オペレータが定義されているかどうかをチェックするには、次の

template<typename T> 
struct has_deref_op{ 
    private: 
     template<typename U> 
     static constexpr auto test(int) -> decltype(std::declval<U>().operator*() == 1, 
                std::true_type()); 

     template<typename U> 
     static constexpr std::false_type test(...); 

    public: 
     static constexpr bool value = std::is_same<decltype(test<T>(0)), 
                std::true_type>::value; 
}; 

このコードはT実装でoperator*が存在するかどうかをチェックします。あなたは、引数を検証するためにそれを使用しますstatic_assertを追加することができます。

template<typename TIterator> 
void PrintRange(TIterator beginIter, TIterator endIter) 
{ 
    static_assert(has_deref_op<TIterator>::value, "argument must implement operator*"); 
    for(auto it = beginIter; it != endIter; ++it) 
    { 
     std::cout << *it << std::endl; 
    } 
} 

このソリューションでは、主要な欠陥を持っている - それはエラーメッセージを簡素化するためにコードのかなり多くを書くことが必要です。正直言って、このアプローチはかなりうまくいくはずですが、私はデフォルトのエラーメッセージに固執します。それは全く自明です。もしintoperator*が定義されていない)を指定すると、そのエラーが発生します。

編集:彼の答えでエドガーがリンクしている質問を読んだ後、このアプローチと同様に動作するis_iteratorを実装することを推奨しているようです。初めてのことを慎重に読んでいないと悪いです。

関連する問題