我有这样的类型:
template<typename T>
struct wrapper
{
using foo = typename T::foo;
using bar = typename T::bar;
using baz = typename T::baz;
// More of those...
};
当且仅当foo
中存在等效类型时,我希望bar
,baz
,T
和等效类型别名定义。使用std::conditional
的解决方案允许在不存在的情况下将其替换为其他内容,但我不知道如何确保在相应的类型不存在时它根本不存在#39; t存在于模板类型中。如果wrapper<T>
未定义其中一个类型别名,则在T
实例化时,上述代码会导致错误。
我无法wrapper
继承T
,因为wrapper
不应该做T
所能做的所有事情。此外,使用部分专业化将导致某种指数爆炸,并很快变得不可维护。我可能会使foo
,bar
...模板类型别名在默认模板参数中注入std::enable_if
,但用户必须编写wrapper<T>::foo<>
,{{1 }而不是wrapper<T>::bar<>
,wrapper<T>::foo
等...我不想要那个。
仅当wrapper<T>::bar
中存在相应的类型别名时,是否有一种简单但可维护的方法来定义此类型别名?
答案 0 :(得分:9)
您可以定义check_foo
,check_bar
和check_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>
添加到包装器继承列表。可能会更糟。
答案 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结构,完全不需要宏包装器。