2017-09-06 16 views
7

同じ基本機能を実装するが、異なるメンバ関数名を持つインターフェイスを持つ複数のクラスに対して再利用したい汎用コードがあるとします。たとえば、基底クラスがeraseメンバ関数を持つ場合、次のコードが動作します。 std::setまたはstd::unordered_setコンパイル時にC++でメンバ関数のエイリアス

template <typename T> 
static std::chrono::duration<double> set_insert_time(const typename T::value_type &v) { 
    T set; 
    std::chrono::time_point<std::chrono::high_resolution_clock> start, end; 
    start = std::chrono::high_resolution_clock::now(); 
    set.erase(v); 
    end = std::chrono::high_resolution_clock::now(); 
    return end - start; 
} 

ただし、この機能を、代わりにunsafe_eraseという名前の関数が提供されています。

私の最初のアプローチは、部分テンプレートの特殊化を使用して、以下を定義し、代わりにset_ops<T>::erase(set, v)を呼び出すことによって、型特性を利用することでした。残念ながら、これはコンパイルされません。 tbb::concurrent_unordered_setはテンプレート型であり、型ではないからです。私はまた、キー型の2番目のテンプレート引数で型の特性を拡張しようとしましたが、Tstd::mem_fn(&T<U>::erase)のテンプレートではないため、コンパイルが失敗します。

template <typename T> 
struct set_ops { 
    constexpr static auto erase = std::mem_fn(&T::erase); 
}; 

template <> 
struct set_ops<tbb::concurrent_unordered_set> { 
    constexpr static auto erase = std::mem_fn(&T::unsafe_erase); 
}; 

また、メンバー関数を次のように関数テンプレートでラップしようとしました。これはコンパイルされているように見えますが、例えません。 decltype ((({parm#1}.erase)({parm#2})),((bool)())) erase<std::set<unsigned int, std::less<unsigned int>, std::allocator<unsigned int> > >(std::set<unsigned int, std::less<unsigned int>, std::allocator<unsigned int> >&, std::set<unsigned int, std::less<unsigned int>, std::allocator<unsigned int> >::key_type const&)

template <typename T> 
constexpr auto set_erase(T& s, const typename T::key_type &v) -> decltype(s.erase(v), bool()); 
template <typename T> 
constexpr auto set_erase(T& s, const typename T::key_type &v) -> decltype(s.unsafe_erase(v), bool()); 

どのように私は、コンパイル時にこのエイリアシングを実行する必要がありますか?私は、基になる各クラスの抽象インタフェースを継承する実装を提供することも、メンバ関数へのポインタを利用することもできますが、実行時のオーバーヘッドを避けたいと思います。

答えて

3

あなただけの部分的な特殊化と一緒にあなたのヘルパー構造体に単純なラッパー関数を供給することができます:これは、メッシングのすべてを回避

template <typename T> 
static std::chrono::duration<double> set_insert_time(const typename T::value_type &v) { 
    T set; 
    std::chrono::time_point<std::chrono::high_resolution_clock> start, end; 
    start = std::chrono::high_resolution_clock::now(); 
    set_ops<T>::erase(set, v); 
    end = std::chrono::high_resolution_clock::now(); 
    return end - start; 
} 

template <typename T> 
struct set_ops { 
    static auto erase(T& t, const T::value_type& obj) { 
    return t.erase(obj); 
    } 
}; 

template <typename... T> 
struct set_ops<tbb::concurrent_unordered_set<T...>> { 
    using set_type = tbb::concurrent_unordered_set<T...>; 
    static auto erase(set_type& t, const typename set_type::value_type& obj) { 
    return t.unsafe_erase(obj); 
    } 
}; 

次に、あなたのset_inert_time関数は次のようになりますコンパイル時にすべてをうまく解決できるようにします。

+1

は不必要に複雑に思えます。 'erase'の2つのバージョンは、過負荷解決によって選択されるフリー関数です。 – MSalters

+0

@MSaltersこれはetherの部分的な特殊化または 'enable_if' SFINEのトリックです。さもなければ、 'erase(tbb :: concurrent_unordered_set &const tbb :: concurrent_unordered_set :: value_type&)'のあいまいなオーバーロードが発生します。 –

0

メンバ関数が統一された署名を持っている限り、メンバ関数へのポインタを非型のテンプレートパラメータまたはコンパイル時のconstexprとして使用できますが、構文は...とにかくC++。

次のコードは、gcc 7.1用にコンパイルされます。私はそれをテストするためのtbbライブラリを持っていませんが、それは他のコンパイラで動作するはずです。

// traits declaration 
template <typename T> struct set_ops; 

// this template use a non type template parameter, assuming the member 
// function's signature is like this: (pseudo code) 
// template <typename T> struct some_set_implementation<T> 
// { iterator_type erase(const value_type &); }; 
template <typename T, typename T::iterator_type (T::*memfn)(const typename T::value_type &) = set_ops<T>::erase> 
static std::chrono::duration<double> set_insert_time(const typename T::value_type &v) { 
    T set; 
    std::chrono::time_point<std::chrono::high_resolution_clock> start, end; 
    start = std::chrono::high_resolution_clock::now(); 
    (set.*memfn)(v); 
    end = std::chrono::high_resolution_clock::now(); 
    return end - start; 
} 

// this code use constexpr 
template <typename T> 
static std::chrono::duration<double> set_insert_time(const typename T::value_type &v) { 
    T set; 
    constexpr auto memfn = set_ops<T>::erase; 
    std::chrono::time_point<std::chrono::high_resolution_clock> start, end; 
    start = std::chrono::high_resolution_clock::now(); 
    (set.*memfn)(v); 
    end = std::chrono::high_resolution_clock::now(); 
    return end - start; 
} 

// here goes specilizations for the type trait 
template <typename T> 
struct set_ops<concurrent_unordered_set<T>> { 
    static constexpr auto erase = &concurrent_unordered_set<T>::unsafe_erase; 
}; 
template <typename T, template <typename> class CONTAINER> 
struct set_ops<CONTAINER<T>> { 
    static constexpr auto erase = &CONTAINER<T>::erase; 
}; 

EDIT:

は、メンバ関数ポインタの狂気を忘れます。

回答を参照してください。非メンバ関数ラッパーは間違いなくよりクリーンな方法です。

1

あなたのコンパイラがコンセプトTSを実装している場合、それはそのような単純なものでした:

template <typename T> 
static std::chrono::duration<double> set_insert_time(const typename T::value_type &v) { 
    T set; 
    std::chrono::time_point<std::chrono::high_resolution_clock> start, end; 
    start = std::chrono::high_resolution_clock::now(); 
    if constexpr(requires{set.erase(v);}) set.erase(v); 
    else set.unsafe_erase(v); 
    end = std::chrono::high_resolution_clock::now(); 
    return end - start; 
} 

とテンプレート機能がインスタンス化される前に、コンセプトをチェックすることで、より良い行うことができます。

+0

知っておいてよかったですが、コンセプトTSは実際に少し使い慣れています。 – ddcc

+0

@ddcc私は新しいものに魅了されています。特に、熟成していても、古いバグのテンプレートコードを解決してください!ツールの品質は、その時代には存在しませんが、それが提供するレバレッジにあります。第二に、1年間でこの答えはあまり前向きではありません。 – Oliv

1

あなたは、単にいくつかのSFINAEでオーバーロードを使用する場合があります。

template <typename F> 
static std::chrono::duration<double> timed_func(F&& f) { 
    std::chrono::time_point<std::chrono::high_resolution_clock> start, end; 
    start = std::chrono::high_resolution_clock::now(); 
    std::forward<F>(f)(); 
    end = std::chrono::high_resolution_clock::now(); 
    return end - start; 
} 


template <typename T> 
static auto set_insert_time(const typename T::value_type &v) 
-> decltype(
    static_cast<void>(std::declval<T&>().erase(v)), 
    std::declval<std::chrono::duration<double>>()) 
{ 
    T set; 
    return timed_func([&](){ set.erase(v); }); 
} 

template <typename T> 
static auto set_insert_time(const typename T::value_type &v) 
-> decltype(
    static_cast<void>(std::declval<T&>().unsafe_erase(v)), 
    std::declval<std::chrono::duration<double>>()) 
{ 
    T set; 
    return timed_func([&](){ set.unsafe_erase(v); }); 
} 
+0

コール・グラフはラムダ関数のために反転されますが、呼び出し側ではなく呼び出し側として 'timed_func'が使用されます。 – ddcc

関連する問題