簡體   English   中英

模擬依賴於參數的模板參數查找

[英]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'.
}

請注意,不會創建類型為Ttraits_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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM