6

I was reading through the C++14 standard's section on templates in an attempt to improve my understanding of the subject, and stumbled across this particular rule:

§ 14.1

12 A template-parameter shall not be given default arguments by two different declarations in the same scope.

[Example:

template<class T = int> class X;
template<class T = int> class X { /∗... ∗/ }; // error  

— end example ]

To my (relatively uninformed) reading, the specification of "same scope" implies the ability to declare templates in different scopes from where they are defined.

According to this article on Dr. Dobbs

C++ identifies five kinds of scope: function, function prototype, local, namespace, and class.

Of those, it's my understanding that:

  • function & (I assume function prototype, since it extends past functions only to declarations) scope cannot contain template declarations
  • local scope falls within function scope, and so has the same limitations as above
  • you cannot (re)declare any member of a class outside of that class' declaration.

Thus, the potential odd case of allowing declarations outside of the defined scope (potentially with altered default arguments, dependent on scope!) seemed to fall squarely on the shoulders of the namespace scope. I experimented a bit:

[Coliru]


Command:

g++ -std=c++1z -O2 -Wall -pedantic -pthread main.cpp && ./a.out

Code:

#include <iostream>

namespace math{ 

    template <int I, int J>
    struct Plus{
        constexpr static int Value_ = I + J;
    };

    template <int I, int J = 5>
    struct Minus; // template declaration.

}

// global-scope template declaration?
//template <int I, int J>
//struct math::Minus; // error: does not name a type (if no declaration @ line 9)
                      // error: invalid use of math::Minus w/o argument list
                      //                             (if declaration @ line 9)


namespace math{

    template <int I, int J>
    struct Minus{
        static int value();
    };

    namespace{ // anonymous namespace

        //template <int I, int J = 5>
        //struct Minus; compiles, but is a declaration of another class,
                       // which I assume hides the math scope class

        // error: incomplete type--I assume this means the above 
        // doesn't declare math::Minus, but math::<anon>::Minus
        //constexpr int twominus5= Minus<2>::value(); 

    } // end anonymous namespace

} // end math namespace

//template <int I, int J>
//class math::Minus; // error: invalid use of math::Minus w/o argument list

template <int I, int J>
int math::Minus<I,J>::value(){return I - J;}


int main()
{
    std::cout 
        << math::Minus<5,1>::value() << std::endl
        << math::Minus<0>::value() << std::endl;
}

Output:

4
-5

...and the declaration rules seemed to conform to what I would have expected before reading this little snippet of the standard. Clearly, my understanding is wrong somewhere. Is it with my initial reading of the c++ standard's template default argument declaration clause as I suspect, or have I missed some method of declaring templates outside of their native scope?

Naturally, an odd corner of the language like this (if it indeed exists) should be used sparingly and with great caution, especially since it would alter the behavior of partially specified templates elsewhere dependent on the most applicable scope (Would it cause name collision issues? How would an otherwise fully-qualified name even resolve in a scope with no default declarations, if there were default parameters in the scope of the template definition?), but it's aroused my curiosity.

I'll use aliases regardless since that's less ambiguous for everyone involved, but as I said above: I'm curious now whether this is an odd language feature I failed utterly to deliberately use, or a non-feature I simply imagined.

Community
  • 1
  • 1
jaggedSpire
  • 4,423
  • 2
  • 26
  • 52
  • I'm sorry, but could you please point out what your question is? Are you looking for cases where the same template is declared in different scopes? E.g. `friend` templates like `struct foo { template friend void bar(); }; template void bar();`, member templates etc.? – dyp Jun 06 '15 at 14:01
  • @dyp I was mostly looking for the canonical way of declaring a template in another scope, as it appeared the standard was implying could be done, since this apparently lets it have different default arguments. – jaggedSpire Jun 06 '15 at 14:15

2 Answers2

2

I'm not sure I can follow your thoughts fully, but I think the standard simply uses overly clear wording. It is probably meant to clarify that the "same" template in a different scope may have different default parameters. Example:

namespace A
{
    template< int = 42 > struct X;
}

namespace B
{
    template< int = 123 > struct X;

    namespace C
    {
        template< int = 0 > struct X;
    }
}

Of course, those templates are not the same template (even thought a beginner may think so at first glance) but they are different templates. The standard's wording is most likely just meant to emphasis this.

One example where you could have different defaults is a template alias with using:

#include <iostream>
#include <type_traits>

namespace A
{
    template< int I = 42 >
    struct X { static void f() { std::cout << I << std::endl; } };
}

namespace B
{
    template< int I = 0 >
    using X = A::X< I >; 
}

int main()
{
    A::X<>::f();
    B::X<>::f();
    static_assert( std::is_same< B::X<>, A::X<0> >::value, "Oops" );
}

Live example

The problem with this is, that it looks like it matches your description at first, but interestingly while B::X<> and A::X<0> are the same type, B::X is currently not the same template as A::X. See this answer for more information.

There is a DR (CWG issue 1286) to fix this, though. The different default parameters OTOH are a concern mentioned in the DR, so even if the DR will be resolved, it might not allow different default values.

Community
  • 1
  • 1
Daniel Frey
  • 55,810
  • 13
  • 122
  • 180
  • So in both cases, it's really more a case of using visually identical names to fake the different defaults, with a side of being able to just use a different class entirely? I read the CWG issue. Does the mention at the bottom mean that the different default arguments might be removed from the standard? – jaggedSpire Jun 06 '15 at 14:40
  • @jaggedSpire First part: Well, that's how I understand it. Second question: Hard to say what an accepted resolution to the CWG issue will look like. Personally I have some sympathy for allowing different default values. I don't see much harm in it, but that, of course, means nothing for the decision the committee will make. – Daniel Frey Jun 06 '15 at 14:47
0

One example of different default arguments in different scopes for the same template is when they are declared in different translation units:

// x.hpp
template <class T> class X;

// a.cpp
#include "x.hpp"
template <class T = int> class X;

// b.cpp
#include "x.hpp"
template <class T = float> class X;

Here, we have two translation units creating two scopes which both declare the same entity (class template X). And in each global scope, we have a new declaration of X with different default template parameters. This is okay because they are in different scopes.

Vaughn Cato
  • 63,448
  • 5
  • 82
  • 132
  • I don't think different translation units are considered different scopes by the standard. In fact, it would create a lot of trouble if a method declaration from a header is in different scopes depending on the translation unit. Hence in the above example, `X` is always in the same (global) scope. Interesting observation still. :) – Daniel Frey Jun 06 '15 at 14:13
  • Interesting. I thought different files being different scopes was a [C thing](http://www.drdobbs.com/cpp/scope-regions-in-c/240002006?pgno=1). – jaggedSpire Jun 06 '15 at 14:41
  • @DanielFrey: I don't see why that would be a problem. If the class has external linkage, then it is the same entity even if it is in different scopes. – Vaughn Cato Jun 06 '15 at 14:47
  • @jaggedSpire: That's true, but many C things are also C++ things. – Vaughn Cato Jun 06 '15 at 14:55
  • 1
    @VaughnCato Consider `template struct X` in one TU, `template struct X` in another. Now in one of them, add a specialization `template<> struct X`. Does it apply to the other TU as well? What about ODR? It looks like pandora's box to me once you start allowing something like this. – Daniel Frey Jun 06 '15 at 14:55
  • @DanielFrey: The specialization would only be declared in one of the two translation units, so it would only apply in one of the two translation units. I'm not sure what ODR issue you are thinking of. – Vaughn Cato Jun 06 '15 at 15:02
  • Scratch that part about the "trouble". But I'd still say that different translation units are not automatically different scopes. They are a scope in some sense, but scopes like namespaces (including the global namespace) can be opened multiple times, see §3.3.6. – Daniel Frey Jun 06 '15 at 15:18
  • @DanielFrey: I believe that each translation unit introduces a new global namespace, and each such global namespace is a separate scope. You are right that other namespaces can span multiple translation units, but I only used global namespaces in my example. – Vaughn Cato Jun 06 '15 at 15:23
  • @VaughnCato §3.3.6/3 clearly says that "The outermost declarative region of a translation unit is also a namespace, called the *global namespace*. [...]" - It therefore behaves like all other namespaces. – Daniel Frey Jun 06 '15 at 15:26
  • @DanielFrey: Ok, I think you are right that it is the same namespace, but I don't think that means it is the same scope. – Vaughn Cato Jun 06 '15 at 15:39
  • @DanielFrey: For example, §3.3.6/3 also states that "The potential scope of such a name begins at its point of declaration (3.3.2) and ends at the end of the translation unit that is its declarative region." – Vaughn Cato Jun 06 '15 at 16:23