22

I have the following problem:

class Base
{
};

class Derived : public Base
{
};

class Different
{
};

class X
{
public:
  template <typename T>
  static const char *func(T *data)
  {
    // Do something generic...
    return "Generic";
  }

  static const char *func(Base *data)
  {
    // Do something specific...
    return "Specific";
  }
};

If I now do

Derived derived;
Different different;
std::cout << "Derived: " << X::func(&derived) << std::endl;
std::cout << "Different: " << X::func(&different) << std::endl;

I get

Derived: Generic
Different: Generic

But what I want is that for all classes derived from Base the specific method is called. So the result should be:

Derived: Specific
Different: Generic

Is there any way I can redesign the X:func(...)s to reach this goal?

EDIT:

Assume that it is not known by the caller of X::func(...) if the class submitted as the parameter is derived from Base or not. So Casting to Base is not an option. In fact the idea behind the whole thing is that X::func(...) should 'detect' if the parameter is derived from Base or not and call different code. And for performance reasons the 'detection' should be made at compile time.

mmmmmmmm
  • 15,269
  • 2
  • 30
  • 55

6 Answers6

18

I found a VERY easy solution!

class Base
{
};

class Derived : public Base
{
};

class Different
{
};

class X
{
private:
  template <typename T>
  static const char *intFunc(const void *, T *data)
  {
    // Do something generic...
    return "Generic";
  }

  template <typename T>
  static const char *intFunc(const Base *, T *data)
  {
    // Do something specific...
    return "Specific";
  }

public:
  template <typename T>
  static const char *func(T *data)
  {
    return intFunc(data, data);
  }
};

This works great and is very slim! The trick is to let the compiler select the correct method by the (otherwise useless) first parameter.

mmmmmmmm
  • 15,269
  • 2
  • 30
  • 55
  • 1
    +1, I should have thought of that. This trick is actually used a lot in STL to select between various versions of algorithms based on the category of passed iterators. – avakar Aug 26 '09 at 08:32
  • 1
    Keep the Boost version in mind, though, it will work with other traits than convertibility. – avakar Aug 26 '09 at 08:34
8

You must use SFINAE for this. In the following code, the first function can be instantiated if and only if you pass something that can't be (implicitly) converted to Base *. The second function has this reversed.

You might want to read up on enable_if.

#include <iostream>
#include <boost/utility/enable_if.hpp>
#include <boost/type_traits.hpp>

class Base {};
class Derived : public Base {};
class Different {};

struct X
{
    template <typename T>
    static typename boost::disable_if<boost::is_convertible<T *, Base *>,
        const char *>::type func(T *data)
    {
        return "Generic";
    }

    template <typename T>
    static typename boost::enable_if<boost::is_convertible<T *, Base *>,
        const char *>::type func(T *data)
    {
        return "Specific";
    }
};

int main()
{
    Derived derived;
    Different different;
    std::cout << "Derived: " << X::func(&derived) << std::endl;
    std::cout << "Different: " << X::func(&different) << std::endl;
}
avakar
  • 32,009
  • 9
  • 68
  • 103
  • Well, `enable_if` is pretty easy to implement. However, `is_convertible` seems to be quite a different story. Personally, I wouldn't want to implement it on my own. – avakar Aug 26 '09 at 08:06
  • Precisely. A lot of it however seems to be workarounds for various non-compliant compilers. Perhaps there is a short elegant version hidden in that file somewhere. I really don't feel like searching for it though, and I'm betting neither do you. I'd go with Boost :-) – avakar Aug 26 '09 at 08:16
  • However, note there can be two notions: pointer convertibility, and universal convertibility. The following tests pointer convertibility: Basically, it is: `template struct is_ptr_convertible { static char(&is(B*))[1]; static char(&is(...))[2]; static bool const value = (sizeof is((D*)0) == 1); };` – Johannes Schaub - litb Aug 26 '09 at 16:57
3

I was looking to setup priorities on overlapping enable_if's, specifically for falling back on calling STL container methods, where my traits were things such as is_assignable is_insterable etc.. with which there is overlap on a number of containers.

I wanted to prioritise assign, if it existed, else use an insert iterator. This is a generic example of what I came up with (modified with infinite levels of priority by some handy folk in the #boost irc channel). It works as the implicit conversion of the priority level ranks the overload below another that is otherwise an equally valid option - removing the ambiguity.

#include <iostream>
#include <string>

template <std::size_t N>
struct priority : priority<N - 1> {};

template <>
struct priority<0> {};

using priority_tag = priority<2>;

template <typename T> 
void somefunc(T x, priority<0>)
{
    std::cout << "Any" << std::endl;
}

template <typename T> 
std::enable_if_t<std::is_pod<T>::value >
somefunc(T x, priority<2>)
{
    std::cout << "is_pod" << std::endl;
}

template <typename T>
std::enable_if_t<std::is_floating_point<T>::value >
somefunc(T x, priority<1>)
{
    std::cout << "is_float" << std::endl;
}

int main()
{
    float x = 1;
    somefunc(x, priority_tag{});
    int y = 1;
    somefunc(y, priority_tag{}); 
    std::string z;
    somefunc(z, priority_tag{});
    return 0;
}

It was also suggested that in C++ 14 I could just use constexpr if statements to achieve the same thing, which was far cleaner if Visual Studio 2015 supported them. Hopefully this will help someone else.

#include <iostream>
#include <string>

template <typename T>
void somefunc(T x)
{
    if constexpr(std::is_floating_point<T>::value) {
      static_assert(std::is_floating_point<T>::value);
      std::cout << "is_float" << std::endl;
    } else if constexpr(std::is_pod<T>::value) {
      static_assert(std::is_pod<T>::value);
      std::cout << "is_pod" << std::endl;
    } else {
      static_assert(!std::is_floating_point<T>::value);
      static_assert(!std::is_pod<T>::value);
      std::cout << "Any" << std::endl;
    }
}

int main()
{
    float x = 1;
    somefunc(x);
    int y = 1;
    somefunc(y); 
    std::string z;
    somefunc(z);
    return 0;
}

// thanks to k-ballo @ #boost!

rmawatson
  • 1,909
  • 12
  • 20
1

The expression:

X::func(derived)

Means that the compiler will generate a declaration and code that effectively has this signature:

static const char *func(Derived *data);

which turns out to be a better match than your:

static const char *func(Base *data);

The template function will be used for anything that is legal for typename, e.g. any class you use as T and it will effectively exclude the Base version of your function from being used, due to compile time policy.

My suggestion is to use specialization in X for your specific types, i.e.:

template <typename T>
  static const char *func(T *data)
  {
    // Do something generic...
    return "Generic";
  }

template <>
  static const char *func(Derived *data) // 'Derived' is the specific type
  {
    // Do something specific...
    return "Specific";
  }

Hope that works!

jscharf
  • 5,829
  • 3
  • 24
  • 16
  • Nope, that doesn't work. Just tried it with gcc. Makes sense since you still get a better match with the generic version. – Troubadour Aug 26 '09 at 07:06
  • Whoops - meant to specialize as static const char *func(Derived *data)...those two should at least be equal in the compiler's eyes. Hopefully it picks the one explicitly specialized... – jscharf Aug 26 '09 at 07:10
  • Works here. Make sure your specialization is outside the class definition. Basically, move it to the parent namespace and replace 'static' with 'inline' and add 'X::' before the function name to get it to compile. – strager Aug 26 '09 at 07:31
  • @strager: Yes, he's updated the code to something that works now. – Troubadour Aug 26 '09 at 07:34
  • 2
    @jscharf: You haven't addressed the "...what I want is that for all classes derived from Base.." part of the question. Are you proposing a specialisation for each possible subclass of Base? – Troubadour Aug 26 '09 at 07:37
  • 2
    Function template specialization is a very bad idea. The rules of resolving are very confusing. Read this: http://www.gotw.ca/publications/mill17.htm – Tadeusz Kopec for Ukraine Aug 26 '09 at 08:54
1

If you are using boost, you can do it with some template metaprogramming:

#include <boost/type_traits/is_base_of.hpp>

class X
{
private:
    template <typename T>
    static const char *generic_func(T *data)
    {
        // Do something generic...
        return "Generic";
    }

    template <typename T>
    static const char *base_func(T *data)
    {
        // Do something specific...
        return "Specific";
    }

public:
    template <typename T>
    static const char* func(T* data)
    {
        if (boost::is_base_of<Base, T>::value)
            return base_func(data);

        return generic_func(data);
    }
};

The is_base_of metafunction is evaluated at compile time and the optimizer will most probably remove the dead branch of the if in the func function. This approach allows you to have more than one specific case.

Bojan Resnik
  • 7,320
  • 28
  • 29
0

Just typecast derived to base

X::func((Base*)&derived)

it works....

harishvk27
  • 163
  • 1
  • 9