条件类型别名定义



我有一个类似的类型:

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

我希望定义foobarbaz和等效类型别名,当且仅当等效类型存在于T中时。使用std::conditional的解决方案允许在它不存在时用其他东西替换它,但当模板类型中不存在相应的类型时,我不知道如何确保它根本不存在。如果T没有定义类型别名之一,则在实例化wrapper<T>时,上面的代码会导致错误。

我不能让wrapper继承自T,因为wrapper不应该做T能做的所有事情。此外,使用部分专业化会导致某种指数爆炸,并很快变得不可维护。我可能会做foobar。。。模板类型别名,以便在默认模板参数中注入std::enable_if,但用户必须编写wrapper<T>::foo<>wrapper<T>::bar<>,而不是wrapper<T>::foowrapper<T>::bar等……我不希望这样。

只有当T中存在相应的类型别名时,是否有一种简单但可维护的方法来定义这样的类型别名?

您可以定义只有类型存在的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>添加到包装继承列表中。可能会更糟。

实时演示

请注意,@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>>);
}

实时示例请注意,他的示例是非常罕见的示例之一,其中libstdc++6.0 SVN中继可以(目前!)做一些libc++3.9 SVN中继不能做的事情。

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