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 ofstd::true_type
orstd::false_type
depending of the validity ofOp<Args...>
detected_t
: alias ofOp<Args...>
ornonesuch
depending of validity ofOp<Args...>
.detected_or
: alias of a struct withvalue_t
which isis_detected
, andtype
which isOp<Args...>
orDefault
depending of validity ofOp<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
How to Store Different Data Types in One List? (C++)
Generic Way to Cast Int to Enum in C++
Reversing Order of Words in a Sentence
Is There 'Byte' Data Type in C++
Are Lambdas Inlined Like Functions in C++
Stl Vector: Moving All Elements of a Vector
C++ Copy-Construct Construct-And-Assign Question
How to Keep My Topmost Window on Top
How to Develop Static for Loop in C++
What Does It Mean to "Poison a Function" in C++
Initial Value of Reference to Non-Const Must Be an Lvalue
Static Const Member Value VS. Member Enum:Which Method Is Better & Why
Error: 'I' Does Not Name a Type with Auto
C++ Access Violation Reading Location 0Xcdcdcdcd Error on Calling a Function
How to Expand Environment Variables in .Ini Files Using Boost
Generate a Plane with Triangle Strips
Is There a Proper 'Ownership-In-A-Package' for 'Handles' Available