Search code examples
c++templatesaccess-control

C++ access control not working for templated objects


Problem

Here's a contrived example of a problem I'm facing. I have a templated object with a map function that creates a new object and operates on private (or protected, it doesn't matter) members within that new object.

template<typename T>
class Foo {
public:
    template<typename R>
    Foo<R> map(std::function<R(std::optional<T>)> &&function) {
        auto mappedValue = function(mValue);
        
        Foo<R> f{};
        f.mValue = mappedValue;
    }
    
private:
    std::optional<T> mValue;
};

This works fine so long as T and R are identical. For example:

int main()
{
    Foo<int> f1{};
    Foo<int> f2 = f1.map<int>([](std::optional<int> value) {
        if (value.has_value()) {
            return value.value() + 1;   
        }
        else {
            return 1;
        }
    });

    return 0;
}

However, the moment I T != R, I run into problems:

int main()
{
    Foo<int> f1{};
    Foo<double> f2 = f1.map<double>([](std::optional<int> value) {
        if (value.has_value()) {
            return value.value() + 1.0;   
        }
        else {
            return 1.0;
        }
    });

    return 0;
}
main.cpp:22:11: error: ‘std::optional Foo::mValue’ is private within this context
         f.mValue = mappedValue;
         ~~^~~~~~
main.cpp:26:22: note: declared private here
     std::optional<T> mValue;
                      ^~~~~~

Question

This is easily understood. C++ access control works on a per-class basis and Foo<int> is not the same class as Foo<double>. I can even fix the problem by adding: friend class Foo<int>. That doesn't scale well, since I don't know what T and R may be.

Does anyone know of a generic way to handle this problem and give Foo<int> access to Foo<double>'s private/protected members?


Solution

  • I figured it out.

    You need to declare your current class as a friend of itself.

    template<typename T>
    friend class Foo;
    

    The entire thing looks like this:

    template<typename T>
    class Foo {
    public:
        template<typename R>
        Foo<R> map(std::function<R(std::optional<T>)> &&function) {
            auto mappedValue = function(mValue);
            
            Foo<R> f{};
            f.mValue = mappedValue;
        }
        
    private:
        std::optional<T> mValue;
    
        template<typename T>
        friend class Foo;
    };