Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to limit scope of `using` statement outside of function in C++?

I want to define some static members of template specialization, like this:

namespace A {

template <> int C<A1::A2::...::MyClass1>::member1_ = 5;
template <> int C<A1::A2::...::MyClass1>::member2_ = 5;
template <> int C<A1::A2::...::MyClass1>::member3_ = 5;

template <> int C<B1::B2::...::MyClass2>::member1_ = 6;
template <> int C<B1::B2::...::MyClass2>::member2_ = 6;
template <> int C<B1::B2::...::MyClass2>::member3_ = 6;

...

}

But to simplify the code (and make it look more structured), I want to do something like this:

namespace A {

{
  using T = A1::A2::...::MyClass1;
  template <> int C<T>::member1_ = 5;
  template <> int C<T>::member2_ = 5;
  template <> int C<T>::member3_ = 5;
}

{
  using T = B1::B2::...::MyClass2;
  template <> int C<T>::member1_ = 6;
  template <> int C<T>::member2_ = 6;
  template <> int C<T>::member3_ = 6;
}

...

}

The compiler gives an error: expected unqualified-id. Are there ways to limit the using scope in an "outer" space?

like image 677
abyss.7 Avatar asked Jun 12 '14 08:06

abyss.7


2 Answers

You cannot nest code blocks (i.e. {...}) outside functions.

Let's say we have this part in common:

namespace A1 { namespace A2 {
  class MyClass1;
}}

namespace B1 { namespace B2 {
  class MyClass2;
}}

namespace A {
template<typename T>
struct C
{
  static int member1_;
  static int member2_;
  static int member3_;
};
}

You can either import the names into namespace A, making them available through the qualification A:::

namespace A {
using A1::A2::MyClass1;
template <> int C<MyClass1>::member1_ = 5;
template <> int C<MyClass1>::member2_ = 5;
template <> int C<MyClass1>::member3_ = 5;

using B1::B2::MyClass2;
template <> int C<MyClass2>::member1_ = 6;
template <> int C<MyClass2>::member2_ = 6;
template <> int C<MyClass2>::member3_ = 6;
}

but I assume that is unwanted and you see this as pollution. So then the only thing you can do is use an extra namespace to decrease the number of :::

namespace A {
namespace T {
using T1 = A1::A2::MyClass1;
using T2 = B1::B2::MyClass2;
}

template <> int C<T::T1>::member1_ = 5;
template <> int C<T::T1>::member2_ = 5;
template <> int C<T::T1>::member3_ = 5;


template <> int C<T::T2>::member1_ = 6;
template <> int C<T::T2>::member2_ = 6;
template <> int C<T::T2>::member3_ = 6;
}

This keeps your namespace A clear of unwanted typenames, although it introduces an "implementation namespace" T (which is a terrible name for a namespace!!!).

A third alternative specializes struct template C for the types you want:

namespace A{
template<>
struct C<A1::A2::MyClass1>
{
  static const int member1_ = 5;
  static const int member2_ = 5;
  static const int member3_ = 5;
};

template<>
struct C<B1::B2::MyClass2>
{
  static const int member1_ = 5;
  static const int member2_ = 5;
  static const int member3_ = 5;
};
}

Note that this requires static const data members. You can also do away with a declaration of the struct template like so:

namespace A {
template<typename T>
struct C;
}

to limit its use (at compile time) to only the types you want. This would me my preferred solution.

like image 165
rubenvb Avatar answered Sep 20 '22 10:09

rubenvb


This is a difficult problem to solve. There are several constraints.

  • template specializations have to happen at namespace scope, this rules out your local scope braces { }.
  • a template specialization lives in the same namespace as the primary template, this rules out local namespace detail1 { } and namespace detail2 { } inside namespace A. Although g++ incorrectly accepts this solution, but Clang correctly rejects this (Note: this was where I was stuck earlier today and temporarily deleted this answer).
  • using directives and declarations pollute the namespace of all clients who include this header
  • templates living in inline namespaces can be specialized inside all their enclosing namespace but declaring namespace A to be inline only works if it is enclosed by both the A and B namespace sequences, which is impossible.

The cleanest approach is to use namespace aliases libA and libB for the lengthy A1::A2::...::AN and B1::B2::...::BN sequences of nested namespaces, and to export those aliases from their headers. This simplifies both client code and the actual template specializations.

#include <iostream>

// file C.h

namespace A {

template<class T> 
struct C 
{ 
    static int member1_, member2_, member3_; 
};

}   // namespace A

// file A.h

namespace A1 { namespace A2 {

struct MyClass1 {};

}} // namespace A1, A2

// export namespace alias to hide implementation details
namespace libA = A1::A2;    

namespace A {

template <> int C<libA::MyClass1>::member1_ = 5;
template <> int C<libA::MyClass1>::member2_ = 5;
template <> int C<libA::MyClass1>::member3_ = 5;

}   // namespace A

// file B.h 

namespace B1 { namespace B2 {

struct MyClass2 {};

}} // namespace B1, B2

// export namespace alias to hide implementation details
namespace libB = B1::B2;    

namespace A {

template <> int C<libB::MyClass2>::member1_ = 6;
template <> int C<libB::MyClass2>::member2_ = 6;
template <> int C<libB::MyClass2>::member3_ = 6;

}   // namespace A

// file main.cpp

int main()
{
    std::cout << A::C<libA::MyClass1>::member1_ << "\n";
    std::cout << A::C<libA::MyClass1>::member2_ << "\n";
    std::cout << A::C<libA::MyClass1>::member3_ << "\n";
    std::cout << A::C<libB::MyClass2>::member1_ << "\n";
    std::cout << A::C<libB::MyClass2>::member2_ << "\n";
    std::cout << A::C<libB::MyClass2>::member3_ << "\n";    
}

Live Example.

Note that there is still a mild sympton of boiler plate in the repetition of the MyClass1 and MyClass2, but the code is much more compact with the namespace aliases in place.

like image 40
TemplateRex Avatar answered Sep 18 '22 10:09

TemplateRex