条件类型别名定义
Conditional type alias definition
我有这样的类型:
template<typename T>
struct wrapper
{
using foo = typename T::foo;
using bar = typename T::bar;
using baz = typename T::baz;
// More of those...
};
当且仅当 T
中存在等效类型时,我希望定义 foo
、bar
、baz
和等效类型别名。使用 std::conditional
的解决方案允许在它不存在时用其他东西替换它,但我不知道如何在模板中不存在相应类型时确保它根本不存在类型。如果 T
未定义类型别名之一,则在实例化 wrapper<T>
时,上面的代码会导致错误。
我不能让 wrapper
继承自 T
因为 wrapper
不应该做 T
能做的所有事情。此外,使用部分专业化会导致某种指数爆炸,并很快变得无法维护。我可能会制作 foo
、bar
... 模板类型别名以在默认模板参数中注入 std::enable_if
,但用户必须编写 wrapper<T>::foo<>
、wrapper<T>::bar<>
而不是 wrapper<T>::foo
、wrapper<T>::bar
等...我不想这样。
有没有一种简单但可维护的方法来定义这样的类型别名,只有当相应的类型别名存在于T
时?
您可以定义 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>
到包装器继承列表。可能更糟。
请注意,@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 主干不能做的非常罕见的例子之一。
这需要为每个要注入的类型添加一个检测器别名和一个持有者结构,并且完全不需要宏包装器。
我有这样的类型:
template<typename T>
struct wrapper
{
using foo = typename T::foo;
using bar = typename T::bar;
using baz = typename T::baz;
// More of those...
};
当且仅当 T
中存在等效类型时,我希望定义 foo
、bar
、baz
和等效类型别名。使用 std::conditional
的解决方案允许在它不存在时用其他东西替换它,但我不知道如何在模板中不存在相应类型时确保它根本不存在类型。如果 T
未定义类型别名之一,则在实例化 wrapper<T>
时,上面的代码会导致错误。
我不能让 wrapper
继承自 T
因为 wrapper
不应该做 T
能做的所有事情。此外,使用部分专业化会导致某种指数爆炸,并很快变得无法维护。我可能会制作 foo
、bar
... 模板类型别名以在默认模板参数中注入 std::enable_if
,但用户必须编写 wrapper<T>::foo<>
、wrapper<T>::bar<>
而不是 wrapper<T>::foo
、wrapper<T>::bar
等...我不想这样。
有没有一种简单但可维护的方法来定义这样的类型别名,只有当相应的类型别名存在于T
时?
您可以定义 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>
到包装器继承列表。可能更糟。
请注意,@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 主干不能做的非常罕见的例子之一。
这需要为每个要注入的类型添加一个检测器别名和一个持有者结构,并且完全不需要宏包装器。