Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Scope and Default Arguments in Template Declarations in C++ : Clarifying Standardese

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.

like image 688
jaggedSpire Avatar asked Jun 06 '15 06:06

jaggedSpire


1 Answers

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.

like image 186
Daniel Frey Avatar answered Oct 17 '22 09:10

Daniel Frey