0

Consider the following situation, where a base class prescribes a function that can be reimplemented in a derived class:

template<typename Impl>
class Base {
public:
    void f(double& value, double param)
    { value = 0.0; }
};

class Implementation : public Base<Implementation> {
public:
    void f(double& value, double param)
    { value = param; }
};

int main()
{
    Implementation imp;
    double value = 0.0;
    imp.f(value, 1.0);
}

Suppose that I want to change the signature of the function f in the base class to double f(double param) and to use that for Base and derived classes. I want to perform this change without breaking immediately all implementation classes. Instead, a provider of an implementation class should get a deprecation warning. I came up with the following:

template<typename Impl>
class Base {
public:
    double f(double param) __attribute__ ((deprecated))
    {
        double v = 0.0;
        static_cast<Impl*>(this)->f(v, param);
        return v;
    }
    void f(double& value, double param)
    { value = 0.0; }
};

// class Implementation as before

int main()
{
    Implementation imp;
    double value = imp.f(1.0);
}

As a result, the Base version of f should only be called if f isn't reimplemented in Implementation with the new signature, yielding the desired deprecation warning.

Obviously, this doesn't compile because the definition of f in Implementation shadows the f in Base:

error: no matching function for call to ‘Implementation::f(double)’
     double value = imp.f(1.0);

One solution would be to add using:

class Implementation : public Base<Implementation> {
public:
    using Base<Implementation>::f;
    void f(double& value, double param)
    { value = param; }
};

That would force the provider of Implementation to change immediately his code which is exactly what should be avoided in the first place. Another possibility would be to change the name f in the new signature. I also would like to avoid this since f has a really good name in my real use case.

So my question is: Can I perform the signature change

  • such that providers of Implementation get a deprecation warning, but their code is not broken immediately, i.e., without changes to Implementation, and
  • without having to rename f?
  • 2
    Just a question, unrelated to the problem: Why isn't the method, in an interface, marked as `virtual`? What's the use of the interface if the methods can't be overriden? Or, better yet, why does it throw an exception, rather than being marked as pure-virtual (which is the go-to way of writing interfaces in C++). – Algirdas Preidžius Nov 30 '16 at 11:48
  • What's wrong with the `using` solution? – Vittorio Romeo Nov 30 '16 at 12:13
  • This is a simplified version of a real case with more complex class interdependencies. Static binding is preferred over dynamic one by using CRTP. I tried to boil it down as much as possible while keeping the essential character. – Bernd Flemisch Nov 30 '16 at 12:16
  • I don't want `using` since the provider of `Implementation` should not be force to change his code immediately. – Bernd Flemisch Nov 30 '16 at 12:17

1 Answers1

0

I could construct a solution that works for me from jrok's answer to the question Check if a class has a member function of a given signature:

#include <iostream>
#include <type_traits>

template<typename, typename T>
struct hasFunctionF {};

template<typename C, typename Ret, typename... Args>
struct hasFunctionF<C, Ret(Args...)> {
private:
    template<typename T>
    static constexpr auto check(T*)
    -> typename
        std::is_same<
            decltype( std::declval<T>().f( std::declval<Args>()... ) ),
            Ret    // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
        >::type;  // attempt to call it and see if the return type is correct

    template<typename>
    static constexpr std::false_type check(...);

    typedef decltype(check<C>(0)) type;

public:
    static constexpr bool value = type::value;
};

template<typename Impl>
class Base {
public:
    template<class T = Impl>
    __attribute__ ((deprecated))
    const typename std::enable_if_t<hasFunctionF<T, void(double&, double)>::value, double> g(double param)
    {
        double v = 0.0;
        static_cast<Impl*>(this)->f(v, param);
        return v;
    }

    template<class T = Impl>
    const typename std::enable_if_t<hasFunctionF<T, double(double)>::value, double> g(double param)
    {
        return static_cast<Impl*>(this)->f(param);
    }
};

class OldImplementation : public Base<OldImplementation> {
public:
    void f(double& value, double param)
    { value = param; }
};

class NewImplementation : public Base<NewImplementation> {
public:
    double f(double param)
    { return param; }
};

int main()
{
    OldImplementation oldImp;
    double value = oldImp.g(1.0);
    std::cout << "old: " << value << std::endl;
    NewImplementation newImp;
    std::cout << "new: " << newImp.g(1.0) << std::endl;
}

I still have to introduce a new function name g in the base class and use that name for calling. But I don't think that this can be avoided without changing user code. The user code is OldImplementation which has not to be changed immediately. Compiling user code that employs the old interface only gives a deprecation warning. That's exactly what I wanted to achieve.

Community
  • 1
  • 1