Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Detecting a function in C++ at compile time

Is there a way, presumably using templates, macros or a combination of the two, that I can generically apply a function to different classes of objects but have them respond in different ways if they do not have a specific function?

I specifically want to apply a function which will output the size of the object (i.e. the number of objects in a collection) if the object has that function but will output a simple replacement (such as "N/A") if the object doesn't. I.e.

NO_OF_ELEMENTS( mySTLMap ) -----> [ calls mySTLMap.size() to give ] ------>  10
NO_OF_ELEMENTS( myNoSizeObj ) --> [ applies compile time logic to give ] -> "N/A"

I expect that this might be something similar to a static assertion although I'd clearly want to compile a different code path rather than fail at build stage.

like image 273
Component 10 Avatar asked Jan 18 '12 14:01

Component 10


2 Answers

From what I understand, you want to have a generic test to see if a class has a certain member function. This can be accomplished in C++ using SFINAE. In C++11 it's pretty simple, since you can use decltype:

template <typename T>
struct has_size {
private:
    template <typename U>
    static decltype(std::declval<U>().size(), void(), std::true_type()) test(int);
    template <typename>
    static std::false_type test(...);
public:
    typedef decltype(test<T>(0)) type;
    enum { value = type::value };
};

If you use C++03 it is a bit harder due to the lack of decltype, so you have to abuse sizeof instead:

template <typename T>
struct has_size {
private:
    struct yes { int x; };
    struct no {yes x[4]; };
    template <typename U>
    static typename boost::enable_if_c<sizeof(static_cast<U*>(0)->size(), void(), int()) == sizeof(int), yes>::type test(int);
    template <typename>
    static no test(...);
public:
    enum { value = sizeof(test<T>(0)) == sizeof(yes) };
};

Of course this uses Boost.Enable_If, which might be an unwanted (and unnecessary) dependency. However writing enable_if yourself is dead simple:

template<bool Cond, typename T> enable_if;
template<typename T> enable_if<true, T> { typedef T type; };

In both cases the method signature test<U>(int) is only visible, if U has a size method, since otherwise evaluating either the decltype or the sizeof (depending on which version you use) will fail, which will then remove the method from consideration (due to SFINAE. The lengthy expressions std::declval<U>().size(), void(), std::true_type() is an abuse of C++ comma operator, which will return the last expression from the comma-separated list, so this makes sure the type is known as std::true_type for the C++11 variant (and the sizeof evaluates int for the C++03 variant). The void() in the middle is only there to make sure there are no strange overloads of the comma operator interfering with the evaluation.

Of course this will return true if T has a size method which is callable without arguments, but gives no guarantees about the return value. I assume wou probably want to detect only those methods which don't return void. This can be easily accomplished with a slight modification of the test(int) method:

// C++11
template <typename U>
static typename std::enable_if<!is_void<decltype(std::declval<U>().size())>::value, std::true_type>::type test(int);
//C++03
template <typename U>
static typename std::enable_if<boost::enable_if_c<sizeof(static_cast<U*>(0)->size()) != sizeof(void()), yes>::type test(int);
like image 179
Grizzly Avatar answered Sep 20 '22 13:09

Grizzly


There was a discussion about the abilities of constexpr some times ago. It's time to use it I think :)

It is easy to design a trait with constexpr and decltype:

template <typename T>
constexpr decltype(std::declval<T>().size(), true) has_size(int) { return true; }

template <typename T>
constexpr bool has_size(...) { return false; }

So easy in fact that the trait loses most of its value:

#include <iostream>
#include <vector>

template <typename T>
auto print_size(T const& t) -> decltype(t.size(), void()) {
  std::cout << t.size() << "\n";
}

void print_size(...) { std::cout << "N/A\n"; }

int main() {
  print_size(std::vector<int>{1, 2, 3});
  print_size(1);
}

In action:

3
N/A
like image 28
Matthieu M. Avatar answered Sep 22 '22 13:09

Matthieu M.