[英]Simulating argument-dependent lookup for template arguments
我最近在寫一些類似於庫的代碼時遇到了這個問題,我認為討論它也可能對其他人有所幫助。
假設我有一個庫,其中包含一些在命名空間中定義的函數模板。 函數模板適用於客戶端代碼提供的類型,其內部工作可以根據為客戶端類型定義的類型特征進行自定義。 所有客戶端定義都在其他名稱空間中。
對於最簡單的示例,庫函數基本上必須看起來像這樣(請注意,所有代碼片段只是一廂情願,沒有編譯):
namespace lib
{
template<typename T> void f()
{
std::cout << traits_for<T>::str() << '\n'; //Use the traits in some way.
}
}
客戶端代碼如下所示:
namespace client
{
struct A { };
template<> std::string traits_for<A>::str() { return "trait value"; }
}
然后有人,某個地方可以打電話
lib::f<client::A>();
一切都神奇地起作用( lib::f()
將在命名空間中找到特征顯式特化,其中聲明了T
的模板參數,就像ADL對函數及其參數一樣)。 目標是使客戶端代碼盡可能簡單地為每個客戶端類定義這些特征(可能有幾個)(可能有很多特征)。
讓我們看看我們可以做些什么來使這項工作。 顯而易見的是在lib
定義traits類主模板,然后為客戶端類型明確地專門化它。 但是客戶端無法在自己的命名空間中定義那些顯式特化; 他們必須退出它,至少到全局命名空間,定義顯式特化,然后重新進入client
命名空間,為了最大的樂趣,可以嵌套。 我想將特征定義保持在每個客戶端類定義附近,因此必須在每個類定義附近完成這個命名空間雜耍。 突然之間,客戶代碼中的單行代碼變成了一個混亂的多線程; 不好。
為了允許在client
命名空間中定義特征,我們可以將traits類轉換為traits函數,可以從lib
調用,如下所示:
traits_for(T())
但是現在我們正在創建一個T
類對象,只是為了讓ADL啟動。這些對象的構造成本很高(甚至在某些情況下甚至是不可能的),所以這也不好。 我們必須繼續使用類型,而不是它們的實例。
放棄和定義作為客戶類成員的特征也不是一種選擇。
只要它不會使client
命名空間中的每個類和特征的定義復雜化(編寫一些代碼,但不是每個定義),那么完成此工作所需的一些管道是可以接受的。
我找到了滿足這些嚴格要求的解決方案,我會在答案中寫出來,但我想知道人們對此的看法:替代方案,對我的解決方案的批評,關於所有這些的評論在實踐中出血明顯或完全無用,作品......
為了根據某些論點找到聲明,ADL看起來是最有希望的方向。 所以,我們必須使用類似的東西
template<typename T> ??? traits_helper(T);
但是我們不能創建T
類型的對象,所以這個函數應該只顯示為未評估的操作數; decltype
想到了decltype
。 理想情況下,我們甚至不應該假設T
的構造函數,所以std::declval
也可能有用:
decltype(traits_helper(std::declval<T>()))
這有什么作用? 好吧,如果幫助器聲明如下,它可以返回實際的traits類型:
template<typename T> traits_for<T> traits_helper(T);
我們剛剛在另一個命名空間中找到了一個類模板特化,基於其參數的聲明。
編輯:根據Yakk的評論, traits_helper()
應該采用T&&
,以便在T
的移動構造函數不可用時允許它工作(該函數可能實際上不被調用,但調用它所需的語義約束必須是滿足)。 這反映在下面的完整示例中。
所有這些都放在一個獨立的例子中,它看起來像這樣:
#include <iostream>
#include <string>
#include <utility>
namespace lib
{
//Make the syntax nicer for library code.
template<typename T> using traits_for = decltype(traits_helper(std::declval<T>()));
template<typename T> void f()
{
std::cout << traits_for<T>::str() << '\n';
}
}
namespace client_1
{
//The following two lines are needed only once in every client namespace.
template<typename> struct traits_for { static std::string str(); };
template<typename T> traits_for<T> traits_helper(T&&); //No definition needed.
struct A { };
template<> std::string traits_for<A>::str() { return "trait value for client_1::A"; }
struct B { };
template<> std::string traits_for<B>::str() { return "trait value for client_1::B"; }
}
namespace client_2
{
//The following two lines are needed only once in every client namespace.
template<typename> struct traits_for { static std::string str(); };
template<typename T> traits_for<T> traits_helper(T&&); //No definition needed.
struct A { };
template<> std::string traits_for<A>::str() { return "trait value for client_2::A"; }
}
int main()
{
lib::f<client_1::A>(); //Prints 'trait value for client_1::A'.
lib::f<client_1::B>(); //Prints 'trait value for client_1::B'.
lib::f<client_2::A>(); //Prints 'trait value for client_2::A'.
}
請注意,不會創建類型為T
或traits_for<T>
對象; 永遠不會調用traits_helper
- 只使用它的聲明。
僅要求客戶將其專業化投入正確的命名空間有什么問題? 如果他們想要使用自己的,他們可以:
namespace client
{
struct A { };
struct traits_for_A {
static std::string str() { return "trait value"; }
};
}
namespace lib
{
template <>
struct traits_for<client::A>
: client::traits_for_A
{ };
}
如果您不希望他們寫出所有內容,甚至可以為您的用戶提供一個宏:
#define PROVIDE_TRAITS_FOR(cls, traits) \
namespace lib { \
template <> struct traits_for<cls> : traits { }; \
}
所以上面就可以了
PROVIDE_TRAITS_FOR(client::A, client::traits_for_A)
ADL太棒了。 把事情簡單化:
namespace lib {
// helpers for client code:
template<class T>
struct default_traits{
using some_type=void;
};
struct no_traits{};
namespace details {
template<class T,class=void>
struct traits:lib::no_traits{};
template<class T>
struct traits<T,decltype(void(
traits_func((T*)0)
))>:decltype(
traits_func((T*)0)
){};
}
template<class T>
struct traits:details::traits<T>{};
}
現在只需添加類型Foo
命名空間:
namespace bob{
// use custom traits impl:
struct foo{};
struct foo_traits{
using some_type=int;
};
foo_traits traits_func(foo const*);
// use default traits impl:
struct bar {};
lib::default_traits<bar> traits_func(bar const*);
// use SFINAE test for any type `T`:
struct baz {};
template<class T>
std::enable_if_t<
std::is_base_of<T,baz>{},
lib::default_traits<T>
>
traits_func(T const*)
}
我們完成了。 定義traits_func
可從foo*
轉換的指針的traits_func
足以注入特征。
如果你沒有寫出這樣的超載,我們會得到一個空的traits
,這是SFINAE友好的。
您可以在重載中返回lib::no_traits
以顯式關閉支持,或者只是編寫與類型匹配的重載。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.