6

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.

Component 10
  • 10,247
  • 7
  • 47
  • 64
  • Possible duplicate: http://stackoverflow.com/questions/257288/is-it-possible-to-write-a-c-template-to-check-for-a-functions-existence – Alessandro Pezzato Jan 18 '12 at 14:39
  • 1
    @AlessandroPezzato: with C++11 knocking at the door, there are new opportunities that have not been explored in the linked answer. – Matthieu M. Jan 18 '12 at 15:08
  • Thanks for all the answers and advice. Sadly I can't use C++11 or Boost in my solution due to the clients limitations, which is a shame. Nevertheless, with the info you've all provided, I was able to get this going and extend it to provide a function to stream out either the size() or "" using a private, embedded template class, partially specialised for the 'true' condition. I am now very pleased!! Thank you all! – Component 10 Jan 20 '12 at 15:07

6 Answers6

13

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);
compor
  • 2,239
  • 1
  • 19
  • 29
Grizzly
  • 19,595
  • 4
  • 60
  • 78
  • interesting... but what's the `void()` for ? – Matthieu M. Jan 18 '12 at 14:55
  • @MatthieuM.: the `void()` is not strictly needed. However it's there to make sure that no strange things happen with overloaded comma operators (unlikely in this case, but that is the general way I use for checking all methodexistances, so I thought I'd keep it in). – Grizzly Jan 18 '12 at 15:01
  • I thought briefly it could be the case, but it seems just more like that it was a cruft forgotten when you added `true_type` :) As for going the `decltype` way, it makes things so easy for functions, that it's easier to define traits as `constexpr` functions or just bypass traits and use it directly, see my answer :) – Matthieu M. Jan 18 '12 at 15:08
10

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
Matthieu M.
  • 287,565
  • 48
  • 449
  • 722
  • I can see the use of `void()` in Grizzly's answer, but here? Seems superfluous, since there will be no second parameter to a "possible" comma operator anyways. A left-over? – Xeo Jan 18 '12 at 15:18
  • I'm not sure that this is the better way to write the trait. Compared to the "common" approach of capsulating it in a struct you have only avoided writing the struct around the methods and putting the result value in an enum or a `constexpr bool`. On the other hand `has_size` has a less desiarable signature this way IMO. Same gores for `print_size`. – Grizzly Jan 18 '12 at 15:22
  • @Xeo: the `void()` in `print_size` is used to give it a returnvalue of `void` instead of `decltype(declval().size())` – Grizzly Jan 18 '12 at 15:23
  • @Grizzly: I see it as a different way. It's certainly new, so I expect some resistance. I gave a few reasons while I think that traits could be moved to functions [here](http://stackoverflow.com/questions/8896637/why-are-type-traits-implemented-with-specialized-template-structs-instead-of-con/8900021#8900021). A few years ago I would have embraced your classes, but now I am trying to explore C++11 possibilities, especially in reducing the clutter and getting new functionalities. – Matthieu M. Jan 18 '12 at 15:52
  • Note that you could reduce the clutter even more by using the trailing return type: `auto print_size(T const& t) -> decltype(t.size(), void())`. – Xeo Jan 18 '12 at 15:55
4

This can be done using a technique called SFINAE. In your specific case you could implement that using Boost.Concept Check. You'd have to write your own concept for checking for a size-method. Alternatively you could use an existing concept such as Container, which, among others, requires a size-method.

Community
  • 1
  • 1
Björn Pollex
  • 75,346
  • 28
  • 201
  • 283
  • I'm surprised this answer didn't get top votes. SFINAE is the way to go here, IMHO. – lapk Jan 18 '12 at 15:07
  • 1
    @AzzA: Even though I dislike the "You can do something like " style of answers, most answers here actually show SFINAE practices... – Sebastian Mach Jan 18 '12 at 15:27
3

You can do something like

template< typename T>
int getSize(const T& t)
{
    return -1;
}

template< typename T>
int getSize( const std::vector<T>& t)
{
    return t.size();
}

template< typename T , typename U>
int getSize( const std::map<T,U>& t)
{
    return t.size();
}

//Implement this interface for 
//other objects
class ISupportsGetSize
{
public:
    virtual int size() const= 0;
};

int getSize( const ISupportsGetSize & t )
{
    return t.size();
}

int main()
{
    int s = getSize( 4 );
    std::vector<int> v;
    s = getSize( v );

    return 0;
}

basically the most generic implementation is always return -1 or "NA" but for vector and maps it will return the size. As the most general one always matches there is never a build time failure

parapura rajkumar
  • 24,045
  • 1
  • 55
  • 85
2

Here you go. Replace std::cout with the output of your liking.

template <typename T>
class has_size
{
    template <typename C> static char test( typeof(&C::size) ) ;
    template <typename C> static long test(...);

public:
    enum { value = sizeof(test<T>(0)) == sizeof(char) };
};

template<bool T>
struct outputter
{
    template< typename C >
    static void output( const C& object )
    {
        std::cout << object.size();
    }
};

template<>
struct outputter<false>
{
    template< typename C >
    static void output( const C& )
    {
        std::cout << "N/A";
    }
};


template<typename T>
void NO_OF_ELEMENTS( const T &object )
{
    outputter< has_size<T>::value >::output( object );
}
Drew Dormann
  • 59,987
  • 13
  • 123
  • 180
1

You could try something like:

#include <iostream>
#include <vector>



template<typename T>                                                                
struct has_size                                                                 
{                                                                                   
  typedef char one;                                                                 
  typedef struct { char a[2]; } two;                                                

  template<typename Sig>                                                            
  struct select                                                                     
  {                                                                                 
  };                                                                                

  template<typename U>                                                              
  static one check (U*, select<char (&)[((&U::size)!=0)]>* const = 0);     
  static two check (...);                                                           

  static bool const value = sizeof (one) == sizeof (check (static_cast<T*> (0)));   
};



struct A{ };
int main ( )
{
    std::cout << has_size<int>::value << "\n";
    std::cout << has_size<A>::value << "\n";
    std::cout << has_size<std::vector<int>>::value << "\n";
}

but you have to be careful, this does neither work when size is overloaded, nor when it is a template. When you can use C++11, you can replace the above sizeof trick by decltype magic

PlasmaHH
  • 15,673
  • 5
  • 44
  • 57