Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Let C++ deduce location of the class/namespace a parameter is defined in

Tags:

c++

c++11

This code compiles without any warnings or errors, and is executable.

template<class T>
struct testclass
{
    template<int I>
    class inner {};
    template<int I>
    void f(inner<I> ) {}
};

int main()
{
    testclass<bool> test;
    test.f(testclass<bool>::inner<3>()); // l. 13
    return 0;
}

Now, what I'd like to do is to omit the testclass:: in line 13:

test.f(inner<3>());

It does not work. Is there anything that I can add in testclass' definition, so that my code works?

C++11 is allowed.

like image 277
Johannes Avatar asked Dec 15 '22 01:12

Johannes


2 Answers

In general, there is no global compiler flag that would allow such a deduction, because that would defeat name scoping.

However, you can typedef (or in case of a template - have a type alias) the inner class outside. In your case it would be:

template <int I> using inner = testclass::inner<I>;

This has to be written in global namespace, not within the testclass.

Note that template alias is a feature of C++11

like image 150
CygnusX1 Avatar answered May 22 '23 06:05

CygnusX1


First, boilerplate.

Index boilerplate:

template<unsigned... Is> struct indexes {typedef indexes<Is...> type;};
template<unsigned Max, unsigned... Is> struct make_indexes:make_indexes<Max-1, Max-1, Is...> {};
template<unsigned... Is> struct make_indexes<0, Is...>:indexes<Is...> {};

A helper class to allow proxy construction without naming the enclosing class:

template<int I, typename... Args>
struct inner_helper {
  std::tuple<Args...> args;
  template<typename T, unsigned... Is>
  T construct(indexes<Is...>) && {
    return { std::forward<Args>(std::get<Is>(args))... };
  }
  template<typename T>
  T construct() && {
    return std::move(*this).template construct<T>( make_indexes<sizeof...(Args)>() );
  }
};

A helper function to give you the syntax you want. Note that inner<3> is now a function call rather than creating an object outside of the enclosing class scope:

template<int I, typename... Args>
inner_helper<I, Args...> inner( Args&&... args ) {
  return {std::forward<Args>(args)...};
}

We augment testclass with two overloads. One takes an inner_helper<int, Args...> and constructs an inner<int> using the Args..., the other takes inner<int>:

template<class T>
struct testclass
{
  template<int I>
  class inner {};
  template<int I>
  void f(inner<I> ) {}
  template<int I, typename... Args>
  void f(inner_helper<I, Args...> h) {
    return f( std::move(h).template construct<inner<I>>() );
  }
};

And finally, the requested syntax at point of use:

int main()
{
  testclass<bool> test;
  test.f(inner<3>()); // l. 13
  return 0;
}

this lets you do the syntax you want (inner<3>()), supports constructing said inner<3> with arbitrary arguments (which get perfect-forwarded), and invokes your actual f(inner<I>) method.

It does require that inner<I> be move-constructable and implicitly constructable from the arguments to inner<I>.

I am assuming that inner<I> is a bit more complex than the type you used above. If it is not more complex, you should consider the SCARY technique of C++ iterators, where the iterator (while an internal type sortof) is actually an external type.

live example

like image 30
Yakk - Adam Nevraumont Avatar answered May 22 '23 05:05

Yakk - Adam Nevraumont