The C++ detection idiom
在阅读wikipedia Substitution failure is not an error 时,其中有 std::is_detected
的描述:
With the standardisation of the detection idiom in the Library fundamental v2 (n4562) proposal, the above code could be re-written as follows:
#include <iostream> #include <type_traits> template<typename T> using has_typedef_foobar_t = typename T::foobar; struct foo { using foobar = float; }; int main() { std::cout << std::boolalpha; std::cout << std::is_detected<has_typedef_foobar_t, int>::value << std::endl; std::cout << std::is_detected<has_typedef_foobar_t, foo>::value << std::endl; }
显然,C++ standard library试图提供detection idiom的标准版。
cppreference The C++ detection idiom
#include <experimental/type_traits>
#include <cstddef>
#include <utility>
template<class T>
using copy_assign_t = decltype(std::declval<T&>() = std::declval<const T&>());
struct Meow
{
};
struct Purr
{
void operator=(const Purr&) = delete;
};
static_assert(std::experimental::is_detected<copy_assign_t, Meow>::value,
"Meow should be copy assignable!");
static_assert(!std::experimental::is_detected_v<copy_assign_t, Purr>,
"Purr should not be copy assignable!");
static_assert(std::experimental::is_detected_exact_v<Meow&, copy_assign_t, Meow>,
"Copy assignment of Meow should return Meow&!");
template<class T>
using diff_t = typename T::difference_type;
template<class Ptr>
using difference_type = std::experimental::detected_or_t<std::ptrdiff_t, diff_t, Ptr>;
struct Woof
{
using difference_type = int;
};
struct Bark
{
};
static_assert(std::is_same<difference_type<Woof>, int>::value,
"Woof's difference_type should be int!");
static_assert(std::is_same<difference_type<Bark>, std::ptrdiff_t>::value,
"Bark's difference_type should be ptrdiff_t!");
int main()
{
}
NOTE:
1、上述code中,没有给出
nonesuch
的定义,在 riptutorial C++ is_detected 中,给出了完整的定义
Example
#include <experimental/type_traits>
#include <cstddef>
template<class T>
using copy_assign_t = decltype(std::declval<T&>() = std::declval<const T&>());
struct Meow { };
struct Purr { void operator=(const Purr&) = delete; };
static_assert(std::experimental::is_detected<copy_assign_t, Meow>::value,
"Meow should be copy assignable!");
static_assert(!std::experimental::is_detected_v<copy_assign_t, Purr>,
"Purr should not be copy assignable!");
static_assert(std::experimental::is_detected_exact_v<Meow&, copy_assign_t, Meow>,
"Copy assignment of Meow should return Meow&!");
template<class T>
using diff_t = typename T::difference_type;
template <class Ptr>
using difference_type = std::experimental::detected_or_t<std::ptrdiff_t, diff_t, Ptr>;
struct Woof { using difference_type = int; };
struct Bark {};
static_assert(std::is_same<difference_type<Woof>, int>::value,
"Woof's difference_type should be int!");
static_assert(std::is_same<difference_type<Bark>, std::ptrdiff_t>::value,
"Bark's difference_type should be ptrdiff_t!");
int main() {}
实现分析
在下面文章中给出了对其实现源代码的分析,可以借鉴。
1、primary template and specialization
primary template:
template<class Default, class AlwaysVoid, template<class ...> class Op, class... Args>
struct detector
{
using value_t = std::false_type;
using type = Default;
};
specialization:
template<class Default, template<class ...> class Op, class... Args>
struct detector<Default, my_void_t<Op<Args...>>, Op, Args...>
{
using value_t = std::true_type;
using type = Op<Args...>;
};
显然,如果Op<Args...>
成功,则会选择specialization
2、template template instantiation-实例化、valid expression
template<class ...> class Op
class... Args
Op<Args...>
是valid expression
显然,它相当于模板化模板实例,类似于模板化function signature。
stackoverflow C++ detection idiom without void_t # A
Since the question was modified and C++ 11 is allowed, I post almost a copy-paste from cppreference, no credit taken.
#include <type_traits>
#include <utility>
#include <iostream>
namespace detail
{
struct nonesuch
{
nonesuch() = delete;
~nonesuch() = delete;
nonesuch(nonesuch const&) = delete;
void operator=(nonesuch const&) = delete;
};
template<class Default, class AlwaysVoid, template<class ...> class Op, class... Args>
struct detector
{
using value_t = std::false_type;
using type = Default;
};
template<typename ... Ts>
struct my_make_void
{
typedef void type;
};
template<typename ... Ts>
using my_void_t = typename my_make_void<Ts...>::type;
template<class Default, template<class ...> class Op, class... Args>
struct detector<Default, my_void_t<Op<Args...>>, Op, Args...>
{
using value_t = std::true_type;
using type = Op<Args...>;
};
}
// namespace detail
template<template<class ...> class Op, class... Args>
using is_detected = typename detail::detector<detail::nonesuch, void, Op, Args...>::value_t;
template<class T>
using copy_assign_t = decltype(std::declval<T&>() = std::declval<const T&>());
struct Meow
{
};
struct Purr
{
void operator=(const Purr&) = delete;
};
int main()
{
std::cerr << is_detected<copy_assign_t, Meow>::value << std::endl;
std::cerr << is_detected<copy_assign_t, Purr>::value << std::endl;
return 0;
}
// g++ test.cpp --std=c++11 -pedantic -Wall -Wextra
NOTE:
1、输出如下:
1 0
riptutorial C++ is_detected
To generalize type_trait creation: based on SFINAE there are experimental traits detected_or
, detected_t
, is_detected
.
With template parameters typename Default
, template <typename...> Op
and typename ... Args
:
1、is_detected
: alias of std::true_type
or std::false_type
depending of the validity of Op<Args...>
NOTE:
Op<Args...>
是 valid expression
2、detected_t
: alias of Op<Args...>
or nonesuch
depending of validity of Op<Args...>
.
3、detected_or
: alias of a struct with value_t
which is is_detected
, and type
which is Op<Args...>
or Default
depending of validity of Op<Args...>
which can be implemented using std::void_t
for SFINAE as following:
C++17
#include <type_traits>
namespace detail
{
template<class Default, class AlwaysVoid, template<class ...> class Op, class... Args>
struct detector
{
using value_t = std::false_type;
using type = Default;
};
template<class Default, template<class ...> class Op, class... Args>
struct detector<Default, std::void_t<Op<Args...>>, Op, Args...>
{
using value_t = std::true_type;
using type = Op<Args...>;
};
// special type to indicate detection failure
struct nonesuch
{
nonesuch() = delete;
~nonesuch() = delete;
nonesuch(nonesuch const&) = delete;
void operator=(nonesuch const&) = delete;
};
}
// namespace detail
template<template<class ...> class Op, class... Args>
using is_detected =
typename detail::detector<detail::nonesuch, void, Op, Args...>::value_t;
template<template<class ...> class Op, class... Args>
using detected_t = typename detail::detector<detail::nonesuch, void, Op, Args...>::type;
template<class Default, template<class ...> class Op, class... Args>
using detected_or = detail::detector<detail::Default, void, Op, Args...>;
NOTE:
1、上述仅仅用到了C++ 17
is_void
Traits to detect presence of method can then be simply implemented:
typename <typename T, typename ...Ts>
using foo_type = decltype(std::declval<T>().foo(std::declval<Ts>()...));
struct C1 {};
struct C2 {
int foo(char) const;
};
template <typename T>
using has_foo_char = is_detected<foo_type, T, char>;
static_assert(!has_foo_char<C1>::value, "Unexpected");
static_assert(has_foo_char<C2>::value, "Unexpected");
static_assert(std::is_same<int, detected_t<foo_type, C2, char>>::value,
"Unexpected");
static_assert(std::is_same<void, // Default
detected_or<void, foo_type, C1, char>>::value,
"Unexpected");
static_assert(std::is_same<int, detected_or<void, foo_type, C2, char>>::value,
"Unexpected");
NOTE:
1、上述code,存在一个非常明确明显的错误:
typename <typename T, typename ...Ts> using foo_type = decltype(std::declval<T>().foo(std::declval<Ts>()...));
应该是:
template <typename T, typename ...Ts> using foo_type = decltype(std::declval<T>().foo(std::declval<Ts>()...));
2、完整测试程序如下:
C++17版本:
#include <type_traits>
#include <utility> // std::declval
#pragma once
namespace detail
{
template<class Default, class AlwaysVoid, template<class ...> class Op, class... Args>
struct detector
{
using value_t = std::false_type;
using type = Default;
};
template<class Default, template<class ...> class Op, class... Args>
struct detector<Default, std::void_t<Op<Args...>>, Op, Args...>
{
using value_t = std::true_type;
using type = Op<Args...>;
};
// special type to indicate detection failure
struct nonesuch
{
nonesuch() = delete;
~nonesuch() = delete;
nonesuch(nonesuch const&) = delete;
void operator=(nonesuch const&) = delete;
};
}
// namespace detail
template<template<class ...> class Op, class... Args>
using is_detected =
typename detail::detector<detail::nonesuch, void, Op, Args...>::value_t;
template<template<class ...> class Op, class... Args>
using detected_t = typename detail::detector<detail::nonesuch, void, Op, Args...>::type;
template<class Default, template<class ...> class Op, class... Args>
using detected_or = detail::detector<Default, void, Op, Args...>;
template<typename T, typename ...Ts>
using foo_type = decltype(std::declval<T>().foo(std::declval<Ts>()...));
struct C1
{
};
struct C2
{
int foo(char) const;
};
template<typename T>
using has_foo_char = is_detected<foo_type, T, char>;
int main()
{
static_assert(!has_foo_char<C1>::value, "Unexpected");
static_assert(has_foo_char<C2>::value, "Unexpected");
static_assert(std::is_same<int, detected_t<foo_type, C2, char>>::value,
"Unexpected");
static_assert(std::is_same<void, // Default
detected_or<void, foo_type, C1, char>>::value,
"Unexpected");
static_assert(std::is_same<int, detected_or<void, foo_type, C2, char>>::value,
"Unexpected");
}
// g++ --std=c++17 test.cpp
C++11版本:
#include <type_traits>
#include <utility> // std::declval
#pragma once
namespace std_ext
{
template <typename ...Ts> struct make_void
{
using type = void;
};
template <typename ...Ts> using void_t = typename make_void<Ts...>::type;
} // namespace detail
namespace detail
{
template<class Default, class AlwaysVoid, template<class ...> class Op, class... Args>
struct detector
{
using value_t = std::false_type;
using type = Default;
};
template<class Default, template<class ...> class Op, class... Args>
struct detector<Default, std_ext::void_t<Op<Args...>>, Op, Args...>
{
using value_t = std::true_type;
using type = Op<Args...>;
};
// special type to indicate detection failure
struct nonesuch
{
nonesuch() = delete;
~nonesuch() = delete;
nonesuch(nonesuch const&) = delete;
void operator=(nonesuch const&) = delete;
};
}
// namespace detail
template<template<class ...> class Op, class... Args>
using is_detected =
typename detail::detector<detail::nonesuch, void, Op, Args...>::value_t;
template<template<class ...> class Op, class... Args>
using detected_t = typename detail::detector<detail::nonesuch, void, Op, Args...>::type;
template<class Default, template<class ...> class Op, class... Args>
using detected_or = detail::detector<Default, void, Op, Args...>;
template<typename T, typename ...Ts>
using foo_type = decltype(std::declval<T>().foo(std::declval<Ts>()...));
struct C1
{
};
struct C2
{
int foo(char) const;
};
template<typename T>
using has_foo_char = is_detected<foo_type, T, char>;
int main()
{
static_assert(!has_foo_char<C1>::value, "Unexpected");
static_assert(has_foo_char<C2>::value, "Unexpected");
static_assert(std::is_same<int, detected_t<foo_type, C2, char>>::value,
"Unexpected");
static_assert(std::is_same<void, // Default
detected_or<void, foo_type, C1, char>>::value,
"Unexpected");
static_assert(std::is_same<int, detected_or<void, foo_type, C2, char>>::value,
"Unexpected");
}
// g++ --std=c++11 test.cpp