2017-04-04 56 views
2

我寫這封信旨在拍攝隨機三維向量類,但我用幾個幾何庫在我的項目(一個包含在三維仿真,一個納入分析框架,其中一個是沒有包含在1 GB以上的框架中......)。每個圖書館都有自己的向量的定義,與同樣的方法不同的名稱,例如具有的getX()的getX(),獲取(0)...拿到第一笛卡爾座標。但是有時會採用通用的命名約定,並且一些方法名稱在兩個或多個庫中是相同的。
我當然希望使用此代碼對任何這些載體的,所以我實現了一個模板類。問題如下:如何使我的代碼適應所有這些方法名稱,而無需爲每個實現專門設計我的類(某些方法名稱相同)?
我設法編寫使用的方法或其他,我現在想推廣到任意數量方法的類。一些東西,說:「如果你有方法1,使用此實現,如果你有方法2,使用另一種,...如果你沒有,那麼編譯錯誤」。模板類取決於模板參數方法

目前該類看起來像(縮小到部分拍攝隨機方向):

// First some templates to test the presence of some methods 
namespace detail_rand { 
    // test if a class contains the "setRThetaPhi" method 
    template<class T> 
    static auto test_setRThetaPhi(int) -> 
    decltype(void(std::declval<T>().setRThetaPhi(0.,0.,0.)), 
      std::true_type{}); 

    template<class T> 
    static auto test_setRThetaPhi(float)->std::false_type; 
} 

// true_type if the class contains the "setRThetaPhi" method 
template<class T> 
struct has_setRThetaPhi : decltype(detail_rand::test_setRThetaPhi<T>(0)) {}; 


// The actual class 
template<class vector> 
class Random 
{ 
// everything is static for easy use, might change later 
private: 
    Random() = delete; 
    Random(Random&) = delete; 

    // the distribution, random generator and its seed 
    static decltype(std::chrono::high_resolution_clock::now().time_since_epoch().count()) theSeed; 
    static std::default_random_engine theGenerator; 
    static std::uniform_real_distribution<double> uniform_real_distro; 

// Shoot a direction, the actual implementation is at the end of the file 
private: // the different implementations 
    static const vector Dir_impl(std::true_type const &); 
    static const vector Dir_impl(std::false_type const &); 

public: // the wrapper around the implementations 
    inline static const vector Direction() { 
    return Dir_impl(has_setRThetaPhi<vector>()); 
    } 
}; 


/// initialisation of members (static but template so in header) 
// the seed is not of cryptographic quality but here it's not relevant 
template<class vector> 
decltype(std::chrono::high_resolution_clock::now().time_since_epoch().count()) 
Random<vector>::theSeed = 
    std::chrono::high_resolution_clock::now().time_since_epoch().count(); 

template<class vector> 
std::default_random_engine Random<vector>::theGenerator(theSeed); 

template<class vector> 
std::uniform_real_distribution<double> Random<vector>::uniform_real_distro(0.,1.); 


/// Implementation of method depending on the actual type of vector 
// Here I use the "setRThetaPhi" method 
template<class vector> 
const vector Random<vector>::Dir_impl(std::true_type const &) 
{ 
    vector v; 
    v.setRThetaPhi(1., 
       std::acos(1.-2.*uniform_real_distro(theGenerator)), 
       TwoPi()*uniform_real_distro(theGenerator)); 
    return std::move(v); 
} 

// Here I use as a default the "SetMagThetaPhi" method 
// but I would like to test before if I really have this method, 
// and define a default implementation ending in a compilation error 
// (through static_assert probably) 
template<class vector> 
const vector Random<vector>::Dir_impl(std::false_type const &) 
{ 
    vector v; 
    v.SetMagThetaPhi(1., 
        std::acos(1.-2.*uniform_real_distro(theGenerator)), 
        TwoPi()*uniform_real_distro(theGenerator)); 
    return std::move(v); 
} 

回答

1

一些東西,說:「如果你有方法1,使用這個實現,如果你有方法2,使用這個另一個...,如果你沒有,那麼編譯錯誤「。

我寫了一篇文章,解釋瞭如何在C++ 11,C++ 14和C++ 17中精確實現您需要的內容:"checking expression validity in-place with C++17"

我將綜合以下C++ 11和C++ 14的解決方案 - 您可以使用它們通過包裝他們內部一個「共同」一個正常化所有你處理的接口。然後,您可以在「通用」界面上實現您的算法。


假設您有:

struct Cat { void meow() const; }; 
struct Dog { void bark() const; }; 

你想創建一個函數模板make_noise(const T& x)調用x.meow()如果有效,否則x.bark()如果有效,否則會產生一個編譯器錯誤。


在C++ 11,可以使用enable_ifdetection idiom

您需要爲每個希望檢查其存在的成員創建一個類型特徵。例如:

template <typename, typename = void> 
struct has_meow : std::false_type { }; 

template <typename T> 
struct has_meow<T, void_t<decltype(std::declval<T>().meow())>> 
    : std::true_type { }; 

下面是使用enable_if後返回類型一個使用例 - 該技術利用了expression SFINAE

template <typename T> 
auto make_noise(const T& x) 
    -> typename std::enable_if<has_meow<T>{}>::type 
{ 
    x.meow(); 
} 

template <typename T> 
auto make_noise(const T& x) 
    -> typename std::enable_if<has_bark<T>{}>::type 
{ 
    x.bark(); 
} 

在C++ 14,可以使用通用lambda表達式static_if實現(這裏有一個talk I gave at CppCon 2016有關可能的一個)執行與命令式的語法檢查。

你需要幾個工具:

// Type trait that checks if a particular function object can be 
// called with a particular set of arguments. 
template <typename, typename = void> 
struct is_callable : std::false_type { }; 

template <typename TF, class... Ts> 
struct is_callable<TF(Ts...), 
    void_t<decltype(std::declval<TF>()(std::declval<Ts>()...))>> 
    : std::true_type { }; 

// Wrapper around `is_callable`. 
template <typename TF> 
struct validity_checker 
{ 
    template <typename... Ts> 
    constexpr auto operator()(Ts&&...) const 
    { 
     return is_callable<TF(Ts...)>{}; 
    } 
}; 

// Creates `validity_checker` by deducing `TF`. 
template <typename TF> 
constexpr auto is_valid(TF) 
{ 
    return validity_checker<TF>{}; 
} 

之後,您可以執行所有檢查的make_noise單超載內:

template <typename T> 
auto make_noise(const T& x) 
{ 
    auto has_meow = is_valid([](auto&& x) -> decltype(x.meow()){ }); 
    auto has_bark = is_valid([](auto&& x) -> decltype(x.bark()){ }); 

    static_if(has_meow(x)) 
     .then([&x](auto) 
      { 
       x.meow(); 
      }) 
     .else_if(has_bark(x)) 
     .then([&x](auto) 
      { 
       x.bark(); 
      }) 
     .else_([](auto) 
      { 
       // Produce a compiler-error. 
       struct cannot_meow_or_bark; 
       cannot_meow_or_bark{}; 
      })(dummy{}); 
} 

一些宏觀魔法if constexpr允許你用C++ 17編寫它:

template <typename T> 
auto make_noise(const T& x) 
{ 
    if constexpr(IS_VALID(T)(_0.meow())) 
    { 
     x.meow(); 
    } 
    else if constexpr(IS_VALID(T)(_0.bark())) 
    { 
     x.bark(); 
    } 
    else 
    { 
     struct cannot_meow_or_bark; 
     cannot_meow_or_bark{}; 
    } 
} 
+0

真棒回答!我希望我早點找到你的(順便說一下)文章,我會節省幾個小時。我明天一定會看你的視頻! – gaFF

0

你可以通過引入自己的名字的操作解決這個問題。通過創建特徵類並專門爲每個庫創建特徵類。事情是這樣的:

template <class Vector> 
struct VectorTraits; 

template <> 
struct VectorTraits<Lib1::Vector> 
{ 
    static auto getX(const Lib1::Vector &v) { return v.GetX(); } 
    // ... etc. 
}; 

template <> 
struct VectorTraits<Lib2::Vector> 
{ 
    static auto getX(const Lib2::Vector &v) { return v.Get(0); } 
    // ... etc. 
}; 

//Usage: 

template <class vector> 
auto norm2(const vector &v) 
{ 
    using V = VectorTraits<vector>; 
    return V::getX(v) * V::getX(v) + V::getY(v) + V::getY(v); 
} 

如果你想在不支持的操作靜態斷言,你可以把它們放到非專業化的模板:

template <class T> 
struct False : std::false_type {}; 

template <class Vector> 
struct VectorTraits 
{ 
    static void getX(const Vector &) 
    { 
    static_assert(False<Vector>::value, "This type does not support getting x"); 
    } 
}; 
+0

我沒有考慮一個新的抽象層,因爲我非常確定我想要的是模板魔法,但這可行!它也可能需要很多工作,因爲在完整的Random類中封裝了很多方法。 – gaFF

+0

@gaFF注意,雖然你沒有封裝「隨機」類的成員,但矢量類的成員。 – Angew

+0

是的,我明白了,我的意思是我必須封裝每個庫至少7個方法,時間至少4個庫。組合總是反對我們。 – gaFF