条件类型别名定义

时间:2016-01-06 11:49:37

标签: c++ templates c++14 template-meta-programming sfinae

我有这样的类型:

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

当且仅当foo中存在等效类型时,我希望barbazT和等效类型别名定义。使用std::conditional的解决方案允许在不存在的情况下将其替换为其他内容,但我不知道如何确保在相应的类型不存在时它根本不存在#39; t存在于模板类型中。如果wrapper<T>未定义其中一个类型别名,则在T实例化时,上述代码会导致错误。

我无法wrapper继承T,因为wrapper不应该做T所能做的所有事情。此外,使用部分专业化将导致某种指数爆炸,并很快变得不可维护。我可能会使foobar ...模板类型别名在默认模板参数中注入std::enable_if,但用户必须编写wrapper<T>::foo<>,{{1 }而不是wrapper<T>::bar<>wrapper<T>::foo等...我不想要那个。

仅当wrapper<T>::bar中存在相应的类型别名时,是否有一种简单但可维护的方法来定义此类型别名?

2 个答案:

答案 0 :(得分:9)

您可以定义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 :(得分:5)

请注意@TartanLlama使用void_t的答案很好。但是,在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中继不能做的事情。

这需要为每个注入类型添加一个检测器别名和一个holder结构,完全不需要宏包装器。