Object slicing when using std::enable_if
Asked Answered
G

2

7

I'm attempting to use std::enable_if to specialise a class if one of it's subclasses has a specific member function defined. Otherwise it should use a default implementation that is defined in the base class.

#include <boost/mpl/list.hpp>
#include <boost/function_types/function_type.hpp>
#include <boost/tti/has_member_function.hpp>

#include <iostream>
#include <type_traits>
#include <memory>

BOOST_TTI_HAS_MEMBER_FUNCTION(f2)

class Base
{
public:
    virtual double f1(double x, double y) const
    {
        std::cout << "Called Base Method" << std::endl;
        return 0.0;
    }
};

template<typename Derived>
class A : public Base
{
public:
    template<typename T = Derived>
    typename std::enable_if
              < has_member_function_f2< T
                                      , double
                                      , boost::mpl::list<double>
                                      , boost::function_types::const_qualified
                                      >::value
              , double
              >::type
    f1(double x, double y) const
    {
        std::cout << "Called Derived Method" << std::endl;
        return static_cast<const Derived* const>(this)->f2(x);
    }
};


class B : public A<B>
{
public:
    double f2(double x) const
    {
        return 1.0;
    }
};

int main()
{
    std::unique_ptr<Base> base_instance( new B );
    std::cout << base_instance->f1(100.0, 10.0) << std::endl;

    B b_instance;
    std::cout << b_instance.f1(100.0, 10.0) << std::endl;

    return 0;
}

I would have expected this to print

Called Derived Method
1
Called Derived Method
1

however instead I get

Called Base Method
0
Called Derived Method
1

so it looks like some object slicing is occurring. I can't for the life of me see why this would be the case, if anyone could help me that would be greatly appreciated.

If it helps in any way this is being compiled with g++ 4.7.2

Gerita answered 15/12, 2014 at 17:2 Comment(2)
what made you think that you should what you expected?Majuscule
I have a feeling that I'm misunderstanding the type that I would be getting out of std::enable_if but I assumed that the type of f1 in B would be double f1(double, double) const and thus should be picked up as the virtual function.Gerita
N
3

@Sebastian's answer explains the problem, but the suggested solution is going to be problematic: you can't specialize a base class template on properties of the derived class using CRTP, since the derived class isn't complete when the base class is instantiated. I would suggest that you instead always override f1 in A, and use tag dispatching to determine whether to dispatch to f2 in the derived class or the default implementation in Base:

template<typename Derived>
class A : public Base
{
    double f1_impl(boost::mpl::true_, double x, double) const
    {
        std::cout << "Called Derived Method\n";
        return static_cast<const Derived*>(this)->f2(x);
    }
    double f1_impl(boost::mpl::false_, double x, double y) const
    {
        return Base::f1(x, y);
    }

public:
    double f1(double x, double y) const override
    {
        using has_f2 = typename has_member_function_f2
            < Derived
            , double
            , boost::mpl::list<double>
            , boost::function_types::const_qualified
            >::type;
        return f1_impl(has_f2{}, x, y);
    }
};

DEMO

Nogas answered 15/12, 2014 at 17:53 Comment(1)
Thanks, I didn't know that tag based dispatching was a thing. This solves my problem :)Gerita
F
8

Template functions cannot be virtual. That also means that a template function will never override a virtual function in the base class, even if its particular instantiation signature happens to match.

To achieve what you want, you need to specialize A as a whole, to provide the member in one version and not in the other.

Fructidor answered 15/12, 2014 at 17:9 Comment(0)
N
3

@Sebastian's answer explains the problem, but the suggested solution is going to be problematic: you can't specialize a base class template on properties of the derived class using CRTP, since the derived class isn't complete when the base class is instantiated. I would suggest that you instead always override f1 in A, and use tag dispatching to determine whether to dispatch to f2 in the derived class or the default implementation in Base:

template<typename Derived>
class A : public Base
{
    double f1_impl(boost::mpl::true_, double x, double) const
    {
        std::cout << "Called Derived Method\n";
        return static_cast<const Derived*>(this)->f2(x);
    }
    double f1_impl(boost::mpl::false_, double x, double y) const
    {
        return Base::f1(x, y);
    }

public:
    double f1(double x, double y) const override
    {
        using has_f2 = typename has_member_function_f2
            < Derived
            , double
            , boost::mpl::list<double>
            , boost::function_types::const_qualified
            >::type;
        return f1_impl(has_f2{}, x, y);
    }
};

DEMO

Nogas answered 15/12, 2014 at 17:53 Comment(1)
Thanks, I didn't know that tag based dispatching was a thing. This solves my problem :)Gerita

© 2022 - 2024 — McMap. All rights reserved.