2016-01-06 72 views
7

我有一個類型是這樣的:條件類型別名定義

template<typename T> 
struct wrapper 
{ 
    using foo = typename T::foo; 
    using bar = typename T::bar; 
    using baz = typename T::baz; 
    // More of those... 
}; 

我想,當且僅當等效類型中存在T被定義foobarbaz和等同類型的別名。使用std::conditional的解決方案允許在不存在的情況下將其替換爲其他內容,但當模板類型中不存在相應類型時,我不知道如何確保它完全不存在。當wrapper<T>被實例化時,如果T未定義其中一個類型別名,則上面的代碼會導致錯誤。

我無法從T使wrapper繼承,因爲wrapper是不應該做的一切T可以做。此外,使用部分專業化會導致某種指數爆炸,並很快變得無法維持。我大概可以製作foobar ...模板類型別名在默認模板參數中注入std::enable_if,但用戶必須編寫wrapper<T>::foo<>,wrapper<T>::bar<>而不是wrapper<T>::foo,wrapper<T>::bar等,我不想這樣做。

只有在T中存在相應的類型別名時,是否有簡單但可維護的方法來定義這種類型別名?

回答

8

您可以定義check_foocheck_barcheck_baz的特徵,其只擁有類型,如果它存在,那麼它們都在wrapper繼承:

template <typename T, typename=void> 
struct check_foo{}; 

template <typename T> 
struct check_foo<T, void_t<typename T::foo>> { 
    using foo = typename T::foo; 
}; 

// ditto for bar, baz, etc. 

template <typename T> 
struct wrapper : 
    check_foo<T>, 
    check_bar<T>, 
    check_baz<T> 
{ }; 

這是每種類型的一個額外的結構,但當然是更好的了你提到的指數版本。你甚至可以使一個宏,如果你是合適的反常:

#define DEFINE_CHECKER(NAME) \ 
    template <typename T, typename=void> struct check_##NAME{}; \ 
    template <typename T> struct check_##NAME<T,void_t<typename T::NAME>> \ 
    { using NAME = typename T::NAME; }; 

DEFINE_CHECKER(foo) 
DEFINE_CHECKER(bar) 
DEFINE_CHECKER(baz) 

太可怕了,我知道,但我想你可能需要付出這樣的代價,如果你真的想wrapper<T>::bar而非wrapper<T>::bar<>。如果使用宏版本,添加新類型意味着只是一個新的DEFINE_CHECKER(newname)並將check_newname<T>添加到包裝器繼承列表。可能更糟。

Live Demo

+1

這就是我想到的解決方案,我準備嘗試它。它不是很漂亮,但是比其他解決方案更容易維護,而不會將無用的東西暴露給用戶。謝謝:) – Morwenn

+0

當。我希望能有比這更好的解決方案。 – Justin

5

注意,使用void_t由@TartanLlama答案是好的,因爲它是。但是,在C++ 17中,很可能會有一些標準庫幫助程序,例如is_detected_v,它們將對void_t進行調用。

#include <experimental/type_traits> 

// helpers to reduce boilerplate 
template<class Tag> 
struct empty_base {}; 

template<template<class> class Holder, template<class> class Op, class Arg> 
using inject_or_t = std::conditional_t 
< 
    std::experimental::is_detected_v<Op, Arg>, 
    Holder<Arg>, 
    empty_base<Op<Arg>> 
>; 

// add detector + holder for every conditional nested type 

template<class T> 
using foo_t = typename T::foo; 

template<class T> 
struct foo_holder { using foo = foo_t<T>; }; 

template<class T> 
using bar_t = typename T::bar; 

template<class T> 
struct bar_holder { using bar = bar_t<T>; }; 

template<class T> 
using baz_t = typename T::baz; 

template<class T> 
struct baz_holder { using baz = baz_t<T>; }; 

// wrapper is now simply: 

template<class T> 
struct wrapper 
: inject_or_t<foo_holder, foo_t, T> 
, inject_or_t<bar_holder, bar_t, T> 
, inject_or_t<baz_holder, baz_t, T> 
{}; 

struct Test 
{ 
    using foo = int; 
    using bar = int; 
    using baz = int; 
}; 

int main() 
{ 
    static_assert(!std::experimental::is_detected_v<foo_t, wrapper<int>>); 
    static_assert(!std::experimental::is_detected_v<bar_t, wrapper<int>>); 
    static_assert(!std::experimental::is_detected_v<baz_t, wrapper<int>>); 

    static_assert(std::experimental::is_detected_v<foo_t, wrapper<Test>>); 
    static_assert(std::experimental::is_detected_v<bar_t, wrapper<Test>>); 
    static_assert(std::experimental::is_detected_v<baz_t, wrapper<Test>>); 
} 

Live Example注意,他是非常罕見的例子,其中的libstdC++ 6.0 SVN主幹可以(現在!)做在libC++ 3.9 SVN主幹可以不是一個。

這需要爲每個要注入的類型添加探測器別名和持有者結構,並且完全消除了對宏包裝器的需要。

+0

真正優雅的解決方案!當然,我們可以通過實施適當的幫助程序或公然地竊取它們來實現C++ 14的功能,例如[this](http://coliru.stacked-crooked.com/a/cee230048c1a807c)。 – TartanLlama

+0

哦,現在這是一個有趣的解決方案: – Morwenn

+0

@TemplateRex雖然這不是@Morwenn所要求的,對吧? 'wrapper '只嘗試三次從'int'繼承,而不是繼承'foo','bar'和'baz'成員別名。 – TartanLlama