Is there a trick that let's one "pass a namespace" into a template?

Viewed 273

The Scenario:

Let's say we have several libraries, each in their own namespace, each containing the exact same set of classes and functions with the same API in every namespace. These libraries have absolutely no knowledge of one another and share no common bases.

namespace A {
class Foo { ... };
class Bar { ... };
class Baz { ... };
// A dozen more types
};

namespace B {
class Foo { ... };
class Bar { ... };
class Baz { ... };
// A dozen more types
};

namespace C {
class Foo { ... };
class Bar { ... };
class Baz { ... };
// A dozen more types
};

I am in no way defending this design, but let's take it as a constraint that can't be changed. There are clearly better ways of doing this, but I'm not at liberty to change that right now.

The Problem:

Given the above, we often want implement higher level code that makes use of these libraries, but we don't want to have to repeat this code for each one. Templates seem like the obvious solution.

template<typename Foo, typename Bar, typename Baz>
class FooBarBazzer
{
    std::unique_ptr<Foo> foo;
    std::vector<Bar> bars;
    Baz baz;
    ...
}

This doesn't look too bad, but it requires us to list Foo, Bar and Baz as template parameters and as the number of types involved has grown, this has quickly lead to some seriously unwieldy template argument lists.

I find myself wanting the following imaginary syntax:

template<namespace ns>
class FooBarBazzer
{
    std::unique_ptr<ns::Foo> foo;
    std::vector<ns::Bar> bars;
    ns::Baz baz;
    ...
}

One potential workaround is to write and maintain a trait struct for each namespace containing a member type for every type in that namespace.

namespace A {
struct Types {
    using Foo = A::Foo;
    using Bar = A::Bar;
    using Baz = A::Baz;
    // A dozen more type aliases
};
}

namespace B {
struct Types {
    using Foo = B::Foo;
    using Bar = B::Bar;
    using Baz = B::Baz;
    // A dozen more type aliases
};
}

namespace C {
struct Types {
    using Foo = C::Foo;
    using Bar = C::Bar;
    using Baz = C::Baz;
    // A dozen more type aliases
};
}

template<typename Types>
class FooBarBazzer
{
    std::unique_ptr<typename Types::Foo> foo;
    std::vector<typename Types::Bar> bars;
    typename Types::Baz baz;
    ...
}

But that seems like a lot of boilerplate. (It's also a lot of typename keywords sprinkled around, which isn't ideal, but I doubt that's avoidable.)

The Question:

Is there some better trick to accomplish this? To tell a template at instantiation time which namespace it should be looking for types in? One that doesn't require writing and maintaining much boilerplate?

I suspect the answer is no, but I'm constantly amazed by the things that experts are able to make the C++ type system do.

1 Answers

Short Answer

There is no way to pass a namespace to a template; however that doesn't mean that there aren't alternatives to your situation.

Long Answer

Although there is no way to pass a namespace specifically to this, there is also no real reason why you can't support your use-case in a generic way. Your suggestion to use traits could definitely work -- but might be an unnecessary manual effort depending on the specifics of your problem.

You mention your concerns of growth for the number of types -- and this could be satisfied with a variadic argument list:

template<typename Foo, typename Bar, typename...Args>
bool DoAllFoosBar(const std::vector<Foo> & foos, const Bar & bar, const Args&...args)
{
    return std::all_of(foos.begin(),
                       foos.end(),
                       [&](const auto & foo){ return IsFooBar(foo, bar, args...); });
}

As long as 1 of the arguments matches something unambiguously from the namespace (which from the sounds of it, it should), it will still call through ADL-qualification -- it just now supports two or more arguments.

Additionally, although you cannot constrain strictly on the namespace itself -- you can constrain the function to only be invocable if IsFooBar is invocable on its arguments -- either through SFINAE or just static_assert.

For example:

// Dummy definition in current scope, so it can find others through ADL
void IsFooBar();

template<typename Foo, typename Bar, typename...Args>
auto DoAllFoosBar(const std::vector<Foo> & foos, const Bar & bar, const Args&...args)
  -> decltype(IsFooBar(foos, bar, args...))
  // ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -- expression sfinae
{
  ...
}

This uses Expression SFINAE to conditionally enable DoAllFoosBar if and only if IsFooBar(...) produces a valid result. Since it also sees IsFooBar in the current scope, it will look for all valid entries through ADL as well. You could also do something similar to turn this into a static_assert as well.

Related