Search code examples
c++encapsulationcrtp

How to fix breaking strong encapsulation rules with CRTP implementation?


I like NVI idiom.

But sometimes I want to reduce vftable cost from NVI idiom. Then I tried to apply CRTP to NVI as following.

template<typename E>
class unary_interface {
public:
    virtual ~unary_interface() = default;

public:
    double operator()(const double x) const
    {
        return static_cast<const E&>(*this).apply(x);   //occurs compile error!!!
    }
};

class square : public unary_interface<square> {
private:
    double apply(const double x) const
    {
        return x * x;
    }
};

But this code occurs compile error.

If I change apply function in private field to public, encapsulation is broken. I got an idea that opaque alias solve this problem as following.

template<typename E>
class unary_interface {
public:
    virtual ~unary_interface() = default;

protected:
    class input_type {
    public:
        explicit input_type(const double x) : _x(x) {}
        operator double() const
        {
            return _x;
        }
    private:
        const double _x;
    };

public:
    double operator()(const double x) const
    {
        return static_cast<const E&>(*this).apply(input_type(x));
    }
};

class square : public unary_interface<square> {
    using base_type = unary_interface<square>;
public:
    double apply(const base_type::input_type& d) const
    {
        const double x = static_cast<const double>(d);
        return x * x;
    }
};

This design keeps to disable to access apply function except from operator() of unary_interface.

At first glance "apply function" is exposed to user code, but apply function is accept only protected opaque alias type that is defined on unary_interface in protected field. I think that this combination is very good and reduce virtual function cost with keeping strong encapsulation.

Does this idea has any defect that I can not find out, and do you have a specific name for this design?


Solution

  • But this code occurs compile error.
    ...
    At first glance "apply function" is exposed to user code, but apply function is accept only protected opaque alias type that is defined on unary_interface in protected field. I think that this combination is very good and reduce virtual function cost with keeping strong encapsulation.

    You can easily solve your strong encapsulation dilemma using friend (and in that case it won't come with any negative consequences):

    class square : public unary_interface<square> {
        friend class unary_interface<square>; // <<<
        double apply(const double x) const // <<< Keep apply() private
        {
            return x * x;
        }
    };
    

    No need to deviate using the protected input_type.

    See Live Demo please.

    Does this idea has any defect that I can not find out, and do you have a specific name for this design?

    Well, the defect is over-complicating the situation and presenting the final user a public function they cannot actually use.

    I don't know if that anti-pattern already was identified and named. That somehow misuses the public interface area by declaring promises that cannot effectively be used at the public API.