2017-09-06 60 views
7

假設我有一些泛型代碼,我希望爲實現相同底層功能但具有不同成員函數名稱的接口的多個類重用。例如,如果基礎類具有erase成員函數(例如, std::setstd::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; 
} 

但是,現在我希望此功能可以與例如tbb::concurrent_unordered_set,它提供了一個名爲unsafe_erase的函數。

我最初的做法是利用部分模板專業化的類型特徵,通過定義以下內容,並調用set_ops<T>::erase(set, v)來代替。不幸的是,這不會編譯,因爲 tbb::concurrent_unordered_set是一個模板類,而不是一個類型。我還嘗試使用第二個鍵類型的模板參數來擴展類型特徵,但由於T不是std::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> 
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功能會是這個樣子:

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; 
} 

這樣就避免了所有搞亂使用成員函數指針,並且在編譯時保持一切都很好解析。

+1

似乎過於複雜。兩個版本的'erase'可以是自由函數,通過重載分辨率來選擇。 – 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; 
}; 

編輯:

忘記成員函數指針瘋狂。

查看Miles的答案。一個非成員函數包裝器肯定是一種更乾淨的方式。

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我有種被新事物吸引的感覺,特別是甚至在使用它們的時候,使用它們解決老舊的靜音模板代碼!工具的質量並不在其年代,而在於其提供的槓桿作用!其次,在一年之內,這個答案的前景將會減少。 – 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

這也適用,雖然現在調用圖由於lambda函數而被反轉,而'timed_func'作爲被調用者而不是調用者。 – ddcc

相關問題