Table of Contents

Last time in my blog post about How to Share Code with Const and Non-Const Functions in C++ I had a custom type declared and defined in one place (like in a header file). Recently, I tried to separate the declaration from implementation, and I got into a situation where one private function template was left.

In this article, I’d like to show you one trick that allowed me to convert this function template into a non-member function without giving up private details of the class.

How it Started

Here’s the initial code (simplified a bit):

class PartsStore {
    
    // private nested type...
    struct Part {
        std::string name_;
        bool isAvailable_ { false };
    }
    
public:
    PartsStore(const std::map<int, Part>& parts) : parts_(parts) { }
    
    bool Contains(std::string_view name) const {
        return FindByNameImpl(name, parts_) != nullptr;
    }
    
    void SetAvailability(std::string_view name, bool isAvailable) {
        auto pPart = const_cast<Part*>(FindByNameImpl(name, parts_));
        if (pPart)
            pPart->isAvailable_ = isAvailable;
    }
    
private:       
    template <typename T>
    static auto FindByNameImpl(std::string_view name, T& container) {
        // implementation...
    }
    
    std::map<int, Part> parts_;    
};

PartsStore operates on a map of nested structures Part. We don’t want to expose this type outside, so it’s declared as private.

I had no problems with moving constructors, Contains and SetAvailability member functions.

But I also moved the template member function - FindByNameImpl and extracted it as a non-member static function.

What’s the trick here?

Look at the converted function:

template <typename T>
static auto FindByNameImpl(std::string_view name, T& container) {
    auto it = std::find_if(begin(container), end(container), 
        [&name](const auto& entry) {
            return entry.second._name == name;
        }
    );

    return it != end(container) ? &it->second : nullptr;
}

It’s declared as a free, non-member template function, but it can access a private nested type! It works on a container of std::map<PartStore::Part>.

During the template instantiation this function gets two versions:

  • one for std::map<PartStore::Part>
  • and another for const std::map<PartStore::Part>

On the other hand, if you tried to write a regular “explicit” function with those types:

static void FindTemp(std::map<int, PartsStore::Part>& container) { }

You’d get the following error:

prog.cc: In function 'void FindTemp(std::map<int, PartsStore::Part>&)':
prog.cc:14:24: error: 'struct PartsStore::Part' is private within this context
   14 |     void FindTemp(std::map<int, PartsStore::Part>& container) { }

It looks like we cannot use a name directly, but the compiler has no problem when creating instances of a function template.

Is that correct?

Read below.

Looking into the Standard

Initially, I thought that this might be a compiler error… lucky me! :) But after checking my example with three major compilers, I came to the conclusion that this is probably a well-defined technique and not an error.

Let’s try to find something in the Standard:

https://eel.is/c++draft/class.access#general-4

[Note 2: Because access control applies to names if access control is applied to a typedef name, only the accessibility of the typedef name itself is considered. The accessibility of the entity referred to by the typedef is not considered.

For example,

class A {
class B { }; public: typedef B BB; }; void f() {
A::BB x; // OK, typedef name A::BB is public A::B y; // access error, A::B is private }

>
>  — *end note*]

And similarly you can write (thanks Andreas Fertig for the code sample!):

```cpp
class Test {
    struct S { int i; }; // private

public:
    S a;  // expose S indirectly as variable a
};

int main() {
    Test t{};

    auto x = t.a; // capture the type of a
    x.i = 4;      // use a
}

You can “capture” the type in the above example, but you cannot use it explicitly. Later the code sample uses x.i which is a public name and thus the compiler doesn’t report any issues.

This is also essential for lambdas:

auto GenLamba(int x) {
    return [x]() { return x*x + 40; };
}

auto lambda = GenLambda(1);
lambda();

Since lambdas are “expanded” as a local function object class types, then we cannot “spell it out”. On the other hand, we know that the compiler generates a public call operator, that’s why there’s no issue executing it.

Summary

See the experimental code here: @Wandbox

I guess it’s a relatively rare situation. Still, when you have a function template in your class, you can try extracting it into a static non-member function and benefit from the access to private/protected details of the class (assuming the other names have public access).

The access control is applied on names, so while you cannot explicitly “say” a private, nested type, the compiler has no issues when using this in template instantiation. And as we’ve seen with a few examples, this ability is quite critical for many techniques: for example, returning a local structure, a local closure type, exposing a nested type…

I’m curious if you have more examples of such use cases.

I know that Jason Turner also had an episode on that recently, so you also can have a look: C++ Weekly - Ep 249 - Types That Shall Not Be Named - YouTube

Acknowledgements: Thanks to Tomasz Kamiński, Andreas Fertig, David Pilarski and My Patreon Group for valuable discussions on this topic.

Comments

Please join the discussion at this reddit/r/cpp thread.