How to Require an Exact Function Signature in the Detection Idiom

How to require an exact function signature in the detection idiom?

With is_detected, you may do:

template <typename T, typename Ret, typename Index>
using subscript_t = std::integral_constant<Ret (T::*) (Index), & T::operator[]>;

template <typename T, typename Ret, typename Index>
using has_subscript = is_detected<subscript_t, T, Ret, Index>;

static_assert(has_subscript<std::vector<int>, int&, std::size_t>::value, "!");
static_assert(!has_subscript<std::vector<int>, int&, int>::value, "!");

Demo


I wrote this in SO Documentation:

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:

  • is_detected: alias of std::true_type or std::false_type depending of the validity of Op<Args...>
  • detected_t: alias of Op<Args...> or nonesuch depending of validity of Op<Args...>.
  • 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:

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

} // namespace detail

// special type to indicate detection failure
struct nonesuch {
nonesuch() = delete;
~nonesuch() = delete;
nonesuch(nonesuch const&) = delete;
void operator=(nonesuch const&) = delete;
};

template <template<class...> class Op, class... Args>
using is_detected =
typename detail::detector<nonesuch, void, Op, Args...>::value_t;

template <template<class...> class Op, class... Args>
using detected_t = typename detail::detector<nonesuch, void, Op, Args...>::type;

template <class Default, template<class...> class Op, class... Args>
using detected_or = detail::detector<Default, void, Op, Args...>;

Traits to detect presence of method can then be simply implemented:

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

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

Detect if function with given signature exists

foo() and bar() give completely different problems.

First of all: bar(), that is a template function.

Given that you want that bar() is a template function (different if is a template operator() of a class/struct), if you want something as

static_assert(foobar(bar, 1));

the best i can imagine, is that foobar() should bee a C-style macro; and a variadic one, to make the things more complicated (but don't ask to me to develop that macro).

This is because you can't pass a template function as function argument because a template function isn't an object but a set of objects.

For foo(), if I understand correctly, the problem is that the detection idiom say true when an argument is convertible to the function argument

static_assert(foobar(foo, 3.4)); // you get true but you want false

and that you want to pass the function as an argument.

This is simple to solve (but I don't think is very useful).

If you declare (no definition needed) a couple of overloaded functions as follows

template <typename ... Args, typename R>
std::true_type fwse (R(*)(Args...), int);

template <typename ..., typename T>
std::false_type fwse (T, long);

and a constexpr variadic template variable

template <typename T, typename ... Args>
constexpr auto func_with_signature_exists_v
= decltype(fwse<Args...>(std::declval<T>(), 0))::value;

you can also write foobar() as follows

template <typename F, typename ... Ts>
constexpr auto foobar (F, Ts const & ...)
{
if constexpr ( func_with_signature_exists_v<F, Ts...> )
return std::true_type{};
else
return std::false_type{};
}

and you get

static_assert( foobar(foo, 7) == true );
static_assert( foobar(foo, 3.4) == false );
static_assert( foobar(foo, 1, 2) == false );

I don't think is very useful because this works when the types for the foo() arguments has to be plain types (no const, no references).

If you can avoid to pass values to foobar(), and if is OK to pass the Args... types directly

static_assert( foobar<int>(foo) == true );
static_assert( foobar<double>(foo) == false );
static_assert( foobar<int, int>(foo) == false );

you can rewrite foobar() as follows

template <typename ... Ts, typename F>
constexpr auto foobar (F)
{
if constexpr ( func_with_signature_exists_v<F, Ts...> )
return std::true_type{};
else
return std::false_type{};
}

and become more flexible (because can accept also const and/or reference types for Ts...).

The following is a full compiling example

#include <type_traits>

void foo(int x)
{ }

template <typename ... Args, typename R>
std::true_type fwse (R(*)(Args...), int);

template <typename ..., typename T>
std::false_type fwse (T, long);

template <typename T, typename ... Args>
constexpr auto func_with_signature_exists_v
= decltype(fwse<Args...>(std::declval<T>(), 0))::value;

template <typename ... Ts, typename F>
constexpr auto foobar (F)
{
if constexpr ( func_with_signature_exists_v<F, Ts...> )
return std::true_type{};
else
return std::false_type{};
}

int main ()
{
static_assert( foobar<int>(foo) == true );
static_assert( foobar<double>(foo) == false );
static_assert( foobar<int, int>(foo) == false );
}

Can we use the detection idiom to check if a class has a member function with a specific signature?

Adapting the ideas of dyp and Jarod42, I've came up with

template<class T, typename... Arguments>
using bar_t = std::conditional_t<
true,
decltype(std::declval<T>().bar(std::declval<Arguments>()...)),
std::integral_constant<
decltype(std::declval<T>().bar(std::declval<Arguments>()...)) (T::*)(Arguments...),
&T::bar
>
>;

Notice that bar_t will be the return type of a bar call. In this way, we stay consistent with the toolkit. We can detect the existence by

static_assert(type_traits::is_detected_v<bar_t, foo, int&&>, "not detected");

However, while this solution does exactly what I intended, I hate that I need to write "so much complicated code" for every method I want to detect. I've asked a new question targeting this issue.

Check if a class has a member function of a given signature

I'm not sure if I understand you correctly, but you may exploit SFINAE to detect function presence at compile-time. Example from my code (tests if class has member function size_t used_memory() const).

template<typename T>
struct HasUsedMemoryMethod
{
template<typename U, size_t (U::*)() const> struct SFINAE {};
template<typename U> static char Test(SFINAE<U, &U::used_memory>*);
template<typename U> static int Test(...);
static const bool Has = sizeof(Test<T>(0)) == sizeof(char);
};

template<typename TMap>
void ReportMemUsage(const TMap& m, std::true_type)
{
// We may call used_memory() on m here.
}
template<typename TMap>
void ReportMemUsage(const TMap&, std::false_type)
{
}
template<typename TMap>
void ReportMemUsage(const TMap& m)
{
ReportMemUsage(m,
std::integral_constant<bool, HasUsedMemoryMethod<TMap>::Has>());
}

Check if class/struct has a specific operator

This kind of traits type almost always takes the following form:

#include <utility>
#include <iostream>
#include <string>

template<class T, class Arg>
struct has_equals_impl
{
template<class U> static
auto test(const U* p)
-> decltype( /* the test is here */
(*p) == std::declval<Arg>(),
/* end of test */
void(), std::true_type());

static auto test(...) -> std::false_type;

using type = decltype(test((const T*)nullptr));

};

// this typedef ensures that the actual type is either std::true_type or std::false_type
template<class T, class Arg> using has_equals = typename has_equals_impl<T, Arg>::type;

int main()
{
// int == int? yes
std::cout << has_equals<int, int>() << std::endl;

// string == int? no
std::cout << has_equals<std::string, int>() << std::endl;
}

Here is an almost-complete suite of binary operator checkers with some tests.

You should get the general idea. Note that I had to create left_shift and right_shift function objects since these don't exist in the standard library already.

#include <utility>
#include <iostream>
#include <string>
#include <algorithm>
#include <cassert>

template<class X, class Y, class Op>
struct op_valid_impl
{
template<class U, class L, class R>
static auto test(int) -> decltype(std::declval<U>()(std::declval<L>(), std::declval<R>()),
void(), std::true_type());

template<class U, class L, class R>
static auto test(...) -> std::false_type;

using type = decltype(test<Op, X, Y>(0));

};

template<class X, class Y, class Op> using op_valid = typename op_valid_impl<X, Y, Op>::type;

namespace notstd {

struct left_shift {

template <class L, class R>
constexpr auto operator()(L&& l, R&& r) const
noexcept(noexcept(std::forward<L>(l) << std::forward<R>(r)))
-> decltype(std::forward<L>(l) << std::forward<R>(r))
{
return std::forward<L>(l) << std::forward<R>(r);
}
};

struct right_shift {

template <class L, class R>
constexpr auto operator()(L&& l, R&& r) const
noexcept(noexcept(std::forward<L>(l) >> std::forward<R>(r)))
-> decltype(std::forward<L>(l) >> std::forward<R>(r))
{
return std::forward<L>(l) >> std::forward<R>(r);
}
};

}

template<class X, class Y> using has_equality = op_valid<X, Y, std::equal_to<>>;
template<class X, class Y> using has_inequality = op_valid<X, Y, std::not_equal_to<>>;
template<class X, class Y> using has_less_than = op_valid<X, Y, std::less<>>;
template<class X, class Y> using has_less_equal = op_valid<X, Y, std::less_equal<>>;
template<class X, class Y> using has_greater_than = op_valid<X, Y, std::greater<>>;
template<class X, class Y> using has_greater_equal = op_valid<X, Y, std::greater_equal<>>;
template<class X, class Y> using has_bit_xor = op_valid<X, Y, std::bit_xor<>>;
template<class X, class Y> using has_bit_or = op_valid<X, Y, std::bit_or<>>;
template<class X, class Y> using has_left_shift = op_valid<X, Y, notstd::left_shift>;
template<class X, class Y> using has_right_shift = op_valid<X, Y, notstd::right_shift>;

int main()
{
assert(( has_equality<int, int>() ));
assert((not has_equality<std::string&, int const&>()()));
assert((has_equality<std::string&, std::string const&>()()));
assert(( has_inequality<int, int>() ));
assert(( has_less_than<int, int>() ));
assert(( has_greater_than<int, int>() ));
assert(( has_left_shift<std::ostream&, int>() ));
assert(( has_left_shift<std::ostream&, int&>() ));
assert(( has_left_shift<std::ostream&, int const&>() ));

assert((not has_right_shift<std::istream&, int>()()));
assert((has_right_shift<std::istream&, int&>()()));
assert((not has_right_shift<std::istream&, int const&>()()));
}

SFINAE: decltype on operator[]

If you want to detect whether a type has a certain function or overloaded operator you have to call that function or operator. This is important because you might have several overloads of a function or operator and overload resolution always depends on the caller.

Here is a small example, based on CppCon 2014: Walter E. Brown "Modern Template Metaprogramming: A Compendium, Part II" on how to detect operator[] in a type.

I have no idea why VC is giving you such a weird error which looks more like a parsing error. I would have expected something like »reference to overloaded function could not be resolved; did you mean to call it?«.

#include <string>
#include <type_traits>
#include <vector>

// in C++17 std::void_t
template < typename... >
using void_t = void;

template < typename T, typename Index >
using subscript_t = decltype(std::declval<T>()[std::declval<Index>()]);

template < typename, typename Index = size_t, typename = void_t<> >
struct has_subscript : std::false_type {};

template < typename T, typename Index >
struct has_subscript< T, Index, void_t< subscript_t<T,Index> > > : std::true_type {};

struct A
{
void operator[](size_t) {}
};

struct B {};

int main ()
{
static_assert(has_subscript< std::vector<int> >::value == true , "!");
static_assert(has_subscript< std::vector<double> >::value == true , "!");
static_assert(has_subscript< A >::value == true , "!");
static_assert(has_subscript< A, std::string >::value == false, "!");
static_assert(has_subscript< B >::value == false, "!");
static_assert(has_subscript< double[5] >::value == true , "!");
static_assert(has_subscript< double* >::value == true , "!");
static_assert(has_subscript< double >::value == false, "!");
}

MSVC SFINAE: Substitution does not fail

You should try:

template<typename T> struct make_void {
using type = void;
};

template<typename T>
using void_t = typename make_void<T>::type;

The information on is_detected might also help.

Why SFINAE doesn't work?

You take U by value, so it requires also the construction of the type.

Pass by const reference to fix that.

You may look at is_detected and have something like:

template <typename T, typename ...Ts>
using call_operator_type = decltype(std::declval<T>()(std::declval<Ts>()...));

template <typename T, typename ... Args>
using has_call_operator = is_detected<call_operator_type, T, Args...>;

Is noreturn part of the signature of a function?

"signature" has a very precise definition. Well, several, depending on the kind of thing you are talking about:

  • ⟨function⟩ name, parameter type list ([dcl.fct]), enclosing namespace (if any), and trailing requires-clause ([dcl.decl]) (if any)
  • ⟨function template⟩ name, parameter type list ([dcl.fct]), enclosing namespace (if any), return type, template-head, and trailing requires-clause ([dcl.decl]) (if any)
  • ⟨function template specialization⟩ signature of the template of which it is a specialization and its template arguments (whether explicitly specified or deduced)
  • ⟨class member function⟩ name, parameter type list ([dcl.fct]), class of which the function is a member, cv-qualifiers (if any), ref-qualifier (if any), and trailing requires-clause ([dcl.decl]) (if any)
  • ⟨class member function template⟩ name, parameter type list ([dcl.fct]), class of which
    the function is a member, cv-qualifiers (if any), ref-qualifier (if any), return
    type (if any), template-head, and trailing requires-clause ([dcl.decl]) (if any)
  • ⟨class member function template specialization⟩ signature of the member function template of which it is a specialization and its
    template arguments (whether explicitly specified or deduced)

Attributes are not in any of them.

[[noreturn]] is also not part of the type. It appertains to the function, not its type.


can one detect that a function is noreturn at the time of compilation?

No. The rule the committee established for attributes is that "compiling a valid program with all instances of a particular attribute ignored must result in a correct interpretation of the original program". That rule would not hold if you can programmatically detect an attribute's presence.


In case it is not, should I adopt a convention an[d] define a tag struct?

It's unclear what use such a tag would have.



Related Topics



Leave a reply



Submit