Oleksa
Oleksa

Reputation: 675

SFINAE for types that don't have particular members

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

Answers (2)

Patrick Roberts
Patrick Roberts

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

songyuanyao
songyuanyao

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";
}

LIVE

Upvotes: 3

Related Questions