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 a class has a method with a given name but any signature
The problem in your code is that you check that a class has method that accept, as first argument a constant reference to an object of the same class
template <class T>
static std::true_type testSignature(bool (T::*)(const T&, double) const);
// .......................................^...........^ same class
but inside Derived
you define a method that receive an object of a different class (Base
)
// ...VVVVVVV object is Derived
class Derived : public Base {
public:
// same interface as base class
bool approx_equal(const Base& other, double tolerance) const;
// .....................^^^^ method accept Base
};
A possible solution is relax the test in HasApproxEqualMethod
to accept also objects of different classes
template <class T, class U>
static std::true_type testSignature(bool (T::*)(const U&, double) const);
// now class and argument are different...^...........^
This way is satisfied also
static_assert(HasApproxEqualMethod<Derived>().value == true, "fail Derived");
If you want avoid at all the signature check, you can try something similar
template <typename T>
constexpr auto haemHelper (T const &, int)
-> decltype( &T::approx_equal, std::true_type{} );
template <typename T>
constexpr std::false_type haemHelper (T const &, long);
template <typename T>
using HasApproxEqualMethod = decltype( haemHelper(std::declval<T>(), 0) );
but, this way, HasApproxEqualMethod<T>
is std::true_type
also when T
has a approx_equal
method with a completely different signature or also when approx_equal
is a simple member (a variable).
Templated check for the existence of a class member function?
Yes, with SFINAE you can check if a given class does provide a certain method. Here's the working code:
#include <iostream>
struct Hello
{
int helloworld() { return 0; }
};
struct Generic {};
// SFINAE test
template <typename T>
class has_helloworld
{
typedef char one;
struct two { char x[2]; };
template <typename C> static one test( decltype(&C::helloworld) ) ;
template <typename C> static two test(...);
public:
enum { value = sizeof(test<T>(0)) == sizeof(char) };
};
int main(int argc, char *argv[])
{
std::cout << has_helloworld<Hello>::value << std::endl;
std::cout << has_helloworld<Generic>::value << std::endl;
return 0;
}
I've just tested it with Linux and gcc 4.1/4.3. I don't know if it's portable to other platforms running different compilers.
Check if class has function with signature
The best way I know of is checking if you can actually call the function and if it returns the type you expect. Here's an example of how to detect if a class C
has a receive
method which takes const Event&
as a parameter and "returns" void
. The detection does not care whether the method is implemented in the class C
directly or in some base class that C
derives from, neither does it care whether there are further defaulted parameters. Adapt as needed.
template< typename C, typename Event, typename = void >
struct has_receive
: std::false_type
{};
template< typename C, typename Event >
struct has_receive< C, Event, typename std::enable_if<
std::is_same<
decltype( std::declval<C>().receive( std::declval<const Event&>() ) ),
void
>::value
>::type >
: std::true_type
{};
Is `void_t` necessary to check if a class has a method with a specific signature?
This question explains in detail how void_t
(otherwise known as the detection idiom) works. The key point is that the specialisation will only be considered if the type of the second template parameter evaluates to void
.
In this case, it just so happens that your print()
method returns void
, so decltype(declval<T>().print())
is also void. But if your print()
returned something else, say bool
, then the specialisation would not match and would not be used.
Detect if class has constructor with signature
How can I extend this to the check I want to perform?
You could use decltype(...)
on an T{...}
expression as follows:
struct Foo
{
Foo(Archive&) { }
};
struct Bar
{
// unsupported
};
template<class T>
using HasUnarchiveConstructorImpl =
decltype(T{std::declval<Archive&>()});
template <class T>
using HasUnarchiveConstructor =
std::experimental::is_detected<HasUnarchiveConstructorImpl, T>;
static_assert(HasUnarchiveConstructor<Foo>::value);
static_assert(!HasUnarchiveConstructor<Bar>::value);
live example on wandbox
Or how can I do it in a different way?
See Howard Hinnant's answer.
Checking function with given signature then compile differently
As far as I can tell, we need class template specialization here. Not even C++20 requires
-clauses can be applied to virtual
functions, so the only thing we can do is have the whole class change.
template<typename T> // using C++20 right now to avoid SFINAE madness
struct Foo {
virtual ~Foo() = default;
virtual std::shared_ptr<T> do_something(int a) = 0;
};
template<std::constructible_from<int> T>
struct Foo<T> {
virtual ~Foo() = default; // to demonstrate the issue of having to duplicate the rest of the class
virtual std::shared_ptr<T> do_something(int a) {
return std::make_shared<T>(a);
}
};
If there is a lot of stuff in Foo
, you can avoid duplicating it with a heap of upfront cost by moving do_something
to its own class.
namespace detail { // this class should not be touched by users
template<typename T>
struct FooDoSomething {
virtual ~FooDoSomething() = default;
virtual std::shared_ptr<T> do_something(int a) = 0;
};
template<std::constructible_from<int> T>
struct FooDoSomething<T> {
virtual ~FooDoSomething() = default;
virtual std::shared_ptr<T> do_something(int a);
};
}
template<typename T>
struct Foo : detail::FooDoSomething<T> {
// other stuff, not duplicated
// just an example
virtual int foo(int a) = 0;
};
namespace detail {
template<std::constructible_from<int> T>
std::shared_ptr<T> FooDoSomething<T>::do_something(int a) {
Foo<T> &thiz = *static_cast<Foo<T>*>(this); // if you need "Foo things" in this default implementation, then FooDoSomething is *definitely* unsafe to expose to users!
return std::make_shared<T>(thiz.foo(a));
}
}
Godbolt
To convert this down from C++20, replace the concept-based specialization with old-type branching:
// e.g. for the simple solution
template<typename T, bool = std::is_constructible_v<T, int>>
struct Foo { // false case
// etc.
};
template<typename T>
struct Foo<T, true> { // true case
// etc.
};
// or do this to FooDoSomething if you choose to use that
A runtime error is much easier, at least in C++17 and up, since you can just use a if constexpr
to avoid compiling the problematic code
template<typename T>
struct Foo {
virtual ~Foo() = default;
virtual std::shared_ptr<T> do_something(int a) {
if constexpr(std::is_constructible_v<T, int>) return std::make_shared<T>(a);
else throw std::logic_error("unimplemented Foo<T>::do_something");
}
};
Related Topics
Convert a String in C++ to Upper Case
Raii and Smart Pointers in C++
Reflection and Refraction Impossible Without Recursive Ray Tracing
Why Is Unsigned Integer Overflow Defined Behavior But Signed Integer Overflow Isn'T
Can a Class Member Function Template Be Virtual
What Xml Parser Should I Use in C++
Difference Between 'Typedef' and 'Using' in C++11
What Exactly Is One Definition Rule in C++
Why Are C Character Literals Ints Instead of Chars
What Is the Lifetime of a Static Variable in a C++ Function
How to Print a List of Elements Separated by Commas
Using Base Pointer Register in C++ Inline Asm
Why Do I Get "Unresolved External Symbol" Errors When Using Templates
What Are All the Common Undefined Behaviours That a C++ Programmer Should Know About