Reputation: 675
Why doesn't the following code compile and what is the most concise solution for enabling templates for types with particular members? Also it compiles if template variable is replaced with an expression, which is used to initialize it, directly.
#include <iostream>
template<class T>
constexpr bool is_rect = std::is_same_v<decltype(T::left, T::top, T::right, T::bottom, void()), void>;
// compiles if is_rect<T> is replaced with the expression directly, but of course it's not a solution
template<class T, std::enable_if_t<is_rect<T>, int> = 0>
void f(T)
{
std::cout << "rect enabled\n";
}
template<class T, std::enable_if_t<std::is_arithmetic_v<T>, int> = 0>
void f(T)
{
std::cout << "arithmetic enabled\n";
}
struct ActualType { float left, top, right, bottom; };
int main()
{
f(ActualType{});
f(int{});
return 0;
}
Upvotes: 3
Views: 98
Reputation: 51886
The problem with your is_rect<T>
is that if T
is missing any of the data members, then the definition is an ill-formed expression, rather than a well-formed expression that evaluates to false.
You can use the is_detected
idiom to instead test whether your template is well-formed:
Try it on godbolt.org: Demo
#include <type_traits>
namespace detail
{
template<class AlwaysVoid, template <class...> class Op, class... Args>
struct detector : std::false_type {};
template<template <class...> class Op, class... Args>
struct detector<std::void_t<Op<Args...>>, Op, Args...> : std::true_type {};
} // namespace detail
template<template <class...> class Op, class... Args>
constexpr bool is_detected_v = detail::detector<void, Op, Args...>::value;
namespace detail
{
template<class T>
using rect_detector = decltype(T::left, T::top, T::right, T::bottom);
} // namespace detail
template<class T>
constexpr bool is_rect = is_detected_v<detail::rect_detector, T>;
In C++20, concepts are the most concise way for enabling templates for types with particular members:
Try it on godbolt.org: Demo
#include <iostream>
#include <type_traits>
template<class T>
concept arithmetic = std::is_arithmetic_v<T>;
template<class T>
concept rect = requires (T t)
{
{ t.left };
{ t.top };
{ t.right };
{ t.bottom };
};
template<rect T>
void f(T)
{
std::cout << "rect enabled\n";
}
template<arithmetic T>
void f(T)
{
std::cout << "arithmetic enabled\n";
}
Upvotes: 2
Reputation: 172924
The problem is that is_rect<T>
is always specified as template argument for std::enable_if_t
as part of the signature of f()
, and it's invalid expression when T
doesn't have particular members.
You can apply partial specialization to make is_rect
gives true
or false
based on the type has particular members or not. e.g.
template<class T, class = void>
constexpr bool is_rect = false;
template<class T>
constexpr bool is_rect<T, std::void_t<decltype(T::left, T::top, T::right, T::bottom)>> = true;
then
template<class T, std::enable_if_t<is_rect<T>, int> = 0>
void f(T)
{
std::cout << "rect enabled\n";
}
Upvotes: 3