1

Would it be possible to disable the Foo() override in the derived class (by means of std::enable_if or some boost magic), in case T is not of a certain type, without having to write a template specialization for class Derived?

Bonus points: could the override be disabled if T does not define a certain method?

Here is my SSCCE:

#include <iostream>
#include <string>

class Base
{
public:
    virtual std::string Foo()
    {
        return "Base";
    }
};

template <typename T>
class Derived : public Base
{
public:
    virtual std::string Foo() override
    {
        return "Derived";
    }
};

int main()
{
    Derived<int> testInt;
    std::cout << testInt.Foo() << std::endl;

    Derived<float> testFloat;
    std::cout << testFloat.Foo() << std::endl;//I would like this to print 'Base'
}

UPDATE:

Thank you for the wonderful solutions, but I wasn't able to adapt them to my real code. The following example should provide a better idea of what I'm trying to achieve:

#include <iostream>
#include <string>

class Object
{
public:
    void Test()
    {
        std::cout << "Test" << std::endl;
    }
};

class EmptyObject
{
};

class Base
{
public:
    virtual std::string Foo()
    {
        return "Base";
    }
};

template <typename T>
class Derived : public Base
{
public:
    virtual std::string Foo() override
    {
        m_object.Test();
        return "Derived";
    }

private:
    T m_object;
};

int main()
{
    Derived<Object> testObject;
    std::cout << testObject.Foo() << std::endl;

    Derived<EmptyObject> testEmpty;
    std::cout << testEmpty.Foo() << std::endl;
}
Mihai Todor
  • 7,465
  • 9
  • 44
  • 77

4 Answers4

1

Instead of template specialize the class, you may template specialize the method directly: (https://ideone.com/gYwt5r)

template<> std::string Derived<float>::Foo() { return Base::Foo(); }

And I only see template specialization of a class to disable future override depending of T by adding final to the virtual method.

Jarod42
  • 173,454
  • 13
  • 146
  • 250
1

I would do this by creating two functions that Derived::Foo can delegate to conditionally based on whether T = float. One would contain the real Derived::Foo implementation, while the other would call Base::Foo.

template <typename T>
class Derived : public Base
{
public:
    virtual std::string Foo() override
    {
        return do_Foo(std::is_same<T, float>{});
    }

private:
    std::string do_Foo(std::false_type)
    {
        return "Derived";
    }
    std::string do_Foo(std::true_type)
    {
        return Base::Foo();
    }
};

Live demo


It seems what you actually want to do is call the Derived<T>::Foo() implementation only if T defines a certain member function, otherwise Base::Foo() should be called. This can be done using expression SFINAE.

template <typename T>
class Derived : public Base
{
public:
    std::string Foo() override
    {
        return do_Foo(true);
    }
private:
    template<typename U = T>
    auto do_Foo(bool)
        -> decltype(std::declval<U>().test(), void(), std::string())
    {
        return "Derived";
    }
    std::string do_Foo(int)
    {
        return Base::Foo();
    }
};

Live demo

In the code above, if the type T does not define a member function named test(), the do_Foo(bool) member function template will not be viable. On the other hand, if T::test() does exist, then do_Foo(bool) will be selected because the boolean value being passed to do_Foo by Foo makes it a better match as compared to do_Foo(int).

A detailed explanation of what's going on within the decltype expression in the trailing return type can be found here.

Praetorian
  • 100,267
  • 15
  • 224
  • 307
  • This looks like a nice solution, but, sadly, it doesn't work in my real code, because do_Foo needs to call a method on an object of type `T`, so I need to try something else... – Mihai Todor Jun 16 '14 at 18:18
  • @MihaiTodor You could forward the object to `do_Foo()`. If you show what the signature of your `Foo()` function looks like, I can update the answer. – Praetorian Jun 16 '14 at 18:20
  • 1
    @MihaiTodor I don't see the problem. Call `m_object.Test();` in `std::string do_Foo(std::false_type)` and don't call it in `std::string do_Foo(std::true_type)`. That solves the problem, doesn't it? – Praetorian Jun 16 '14 at 18:27
  • Ah, darn it! I forgot that VS2012 does not support uniform initialization. Thanks a lot! It worked. – Mihai Todor Jun 16 '14 at 18:37
  • 1
    @MihaiTodor I've updated the answer. When I first answered, I missed the part where your real requirement is to call `Derived:Foo()` only if `T` defines a particular member function. Edit: The updated stuff may not work on VS since it doesn't support expression SFINAE yet. – Praetorian Jun 16 '14 at 18:44
  • Wow, that is indeed awesome! Unfortunately, I'll have to make my code work on VS2012, which spits out "error C4519: default template arguments are only allowed on a class template" on your last example. Anyhow, in decltype, why is `void()` required? As far as I can understand, decltype will return the type of the last expression in the comma separated list. – Mihai Todor Jun 16 '14 at 19:10
  • 1
    @MihaiTodor Check the answer I linked to at the end. The explanation was long enough that I didn't want to copy-paste it all over again. – Praetorian Jun 16 '14 at 19:12
  • Ah, how cute :) Who would have thought that someone might overload the comma operator?... I think I should be able to make my code work now. Thank you very much for all your help help. – Mihai Todor Jun 16 '14 at 19:16
1

If you need to restrict a certain type at compile time, you can use std::enable_if together with std::is_same :

typename std::enable_if<std::is_same<T, float>::value, std::string>::type 
virtual Foo() override
{
    return "Derived";
}

Or you can easily redirect the call to the Base method if the template type is not the type you are looking for, still with std::is_same :

virtual std::string Foo() override
{
    return std::is_same<T, float>::value ? Base::Foo() : "Derived";
}

As for the Bonus, you can get the trait from this SO answer, adapted here with decltype, for a method bar() :

template <typename T>
class has_bar
{
    typedef char one;
    typedef long two;

    template <typename C> static one test(decltype(&C::bar) ) ;
    template <typename C> static two test(...);

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

The limitation is that you can't put constraints on the arguments or return types.

virtual std::string Foo() override
{
    return has_bar<T>::value ? "Derived" : Base::Foo() ;
}

Note:

You could also use has_bar together with enable_if as in my first example, to disable it a compile time.

Community
  • 1
  • 1
quantdev
  • 22,595
  • 5
  • 47
  • 84
  • This looks promising, but I think I'm doing something wrong with enable_if: https://ideone.com/MTWBu8 – Mihai Todor Jun 16 '14 at 18:14
  • 1
    No you are not : it does not **compile** because enable_if removed the method (see me post, it is in my first line), thats the point of enable_if (use the other methods if you still want it to compile when used with the unwanted type) – quantdev Jun 16 '14 at 18:16
  • Oh, right. I see now. Sadly, I don't think I will be able to get the redirection trick to work in my case. This example should be closer to the real situation: https://ideone.com/C6g6tx – Mihai Todor Jun 16 '14 at 18:24
  • This is related to the "Bonus" right ? you can use the trait has_bar and change is to has_test exactly the same way. Does that make sense ? Do you want an edit ? – quantdev Jun 16 '14 at 18:28
  • If you could spare some time, I'd love to see a working example. For now, I think I'll be able to achieve my goals with @Praetorian's solution. Thank you very much for your effort! – Mihai Todor Jun 16 '14 at 18:38
1

You can add an intermediate class to your hierarchy:

class Base
{
public:
    virtual std::string Foo()
    {
        return "Base";
    }
};

template <typename T>
class Intermediate : public Base
{
    // common operations with m_object

protected: // not private!
    T m_object;
};

template <typename T, typename = bool>
class Derived : public Intermediate<T> {};

template <typename T>
class Derived<T, decltype(std::declval<T>().Test(), void(), true)>
    : public Intermediate<T>
{
public:
    virtual std::string Foo() override
    {
        this->m_object.Test(); // this-> is necessary here!
        return "Derived";
    }
};

The full example compiles successfully with both clang 3.4 and g++ 4.8.2.

Constructor
  • 6,903
  • 2
  • 17
  • 55
  • Yes, indeed, but I was trying to avoid this, due to the fact that my modifications lie somewhere deep in legacy code, and I might break something... Also, VS2012 doesn't seem to like the expression SFINAE trick, which is a shame, so I ended up using `std::is_same` in order to detect the type for which to call `Test()`, which is something I don't like to do in generic code, but I don't want to mess with the `sizeof` method detection trick right now... – Mihai Todor Jun 16 '14 at 20:25