Why are default template arguments only allowed on class templates?
Asked Answered
C

5

199

Why are default template arguments only allowed on class templates? Why can't we define a default type in a member function template? For example:

struct my_class {
  template<class T = int>
  void mymember(T* vec) {
    // ...
  }
};

Instead, C++ forces that default template arguments are only allowed on a class template.

Crossfade answered 15/3, 2010 at 13:36 Comment(6)
For the first three posted answers, consider this example: struct S { template <class R = int> R get_me_R() { return R(); } }; The template parameter can't be deduced from the context.Ashien
Good question. 3 people have already answered to say it "doesn't make sense", and they're all wrong in general. Function template parameters are not always deductible from the function call parameters. For example, if they were allowed I could write template <int N = 1> int &increment(int &i) { i += N; return i; }, and then increment(i); or increment<2>(i);. As it is, I have to write increment<1>(i);.Baum
Actually, mine and AraK's examples can both be dealt with by overloading. litb's can't, I think, because the template parameter might be deduced or might be specified.Baum
@Steve, hmm you can write template<typename Iterator> void sort(Iterator beg, Iterator end) { sort(beg, end, std::less<Iterator>()); } and write the three-args as an overload. I think it's done that way in today's std::sort. (aww, i should have passed value_type to std::less xD)Schuman
std::sort is done that way now, but your definition allows something that the current definition doesn't - specifying 2 (of 2) template parameters but only 2 (of 3) function call arguments. But I wrongly thought that means your example can't be done with overloads, actually it merely isn't done with overloads currently since the 2-template-param overload doesn't have a default value for its 3rd param. The example in the defect report can be done with overloads (I checked, finding a missing ; in the example text in the process).Baum
@Steve: The missing semicolon is actually a new EOL operator overload to complement B. Stavtrup's "Overloading of C++ Whitespace" published in Journal of Object-Oriented Programming, April 1, 1992. (www2.research.att.com/~bs/papers.html)Nl
S
155

It makes sense to give default template arguments. For example you could create a sort function:

template<typename Iterator, 
         typename Comp = std::less<
            typename std::iterator_traits<Iterator>::value_type> >
void sort(Iterator beg, Iterator end, Comp c = Comp()) {
  ...
}

C++0x introduces them to C++. See this defect report by Bjarne Stroustrup: Default Template Arguments for Function Templates and what he says

The prohibition of default template arguments for function templates is a misbegotten remnant of the time where freestanding functions were treated as second class citizens and required all template arguments to be deduced from the function arguments rather than specified.

The restriction seriously cramps programming style by unnecessarily making freestanding functions different from member functions, thus making it harder to write STL-style code.

Schuman answered 15/3, 2010 at 13:47 Comment(10)
@Arman, the defect report link contains the changes that are made to the working draft for C++0x and the discussions. Arguments neither deduced nor explicitly specified are obtained from default arguments. GCC4.4 supports default arguments for function templates in C++0x mode.Schuman
Nothing to do with the question or answer, but Herb Sutter called the upcomming standard C++11 after last saturdays meeting. I just read it today and feel like sharing :) herbsutter.wordpress.com/2010/03/13/…Blear
and the obligatory follow up question... when is this expected to make it's way into other compilers :)Jenevajeni
@JohannesSchaub-litb I had the same problem: no possibility to speficy the default type in a template function. I have solved by an explicit instantiation of the function on the default type (double in my case). Perhaps it is not "general", but is there any drawback with this practice? Thanks.Commerce
The following code fails to compile, with errors like error: invalid conversion from ‘int’ to ‘int*’, any idea why: ` #include <array> #include <algorithm> #include <functional> template<typename Iterator, typename Comp = std::less<Iterator> > void my_sort(Iterator beg, Iterator end, Comp c = Comp()) { std::sort(beg, end, c); } int main() { std::array<int, 5> ar{5,2,21,7,4}; my_sort(ar.begin(), ar.end()); } `Penurious
@hazelnusse please make a new stackoverflow question for that. the compiler error in that snippet is orthogonal to this question I answered.Schuman
@JohannesSchaub-litb, while you are technically correct, most sorts don't compare iterators but instead the values pointed to by the iterators. Changing std::less<Iterator> to std::less<typename std::iterator_traits<Iterator>::value_type> fixes the issue.Penurious
@hazelnusse ah I'm sorry I didn't notice the code you gave me was partly the code of my answer. Of course there was a technical error in my code. FixedSchuman
Using modern C++ the template header can be simplified to just template<typename Iterator, typename Comp = std::less<>>.Blimp
Is your answer still valid with C++11 C++14 or C++17?Landwaiter
U
38

To quote C++ Templates: The Complete Guide (page 207):

When templates were originally added to the C++ language, explicit function template arguments were not a valid construct. Function template arguments always had to be deducible from the call expression. As a result, there seemed to be no compelling reason to allow default function template arguments because the default would always be overridden by the deduced value.

Unclose answered 15/3, 2010 at 13:48 Comment(0)
B
18

So far, all the proffered examples of default template parameters for function templates can be done with overloads.

AraK:

struct S { 
    template <class R = int> R get_me_R() { return R(); } 
};

could be:

struct S {
    template <class R> R get_me_R() { return R(); } 
    int get_me_R() { return int(); }
};

My own:

template <int N = 1> int &increment(int &i) { i += N; return i; }

could be:

template <int N> int &increment(int &i) { i += N; return i; }
int &increment(int &i) { return increment<1>(i); }

litb:

template<typename Iterator, typename Comp = std::less<Iterator> >
void sort(Iterator beg, Iterator end, Comp c = Comp())

could be:

template<typename Iterator>
void sort(Iterator beg, Iterator end, std::less<Iterator> c = std::less<Iterator>())

template<typename Iterator, typename Comp >
void sort(Iterator beg, Iterator end, Comp c = Comp())

Stroustrup:

template <class T, class U = double>
void f(T t = 0, U u = 0);

Could be:

template <typename S, typename T> void f(S s = 0, T t = 0);
template <typename S> void f(S s = 0, double t = 0);

Which I proved with the following code:

#include <iostream>
#include <string>
#include <sstream>
#include <ctype.h>

template <typename T> T prettify(T t) { return t; }
std::string prettify(char c) { 
    std::stringstream ss;
    if (isprint((unsigned char)c)) {
        ss << "'" << c << "'";
    } else {
        ss << (int)c;
    }
    return ss.str();
}

template <typename S, typename T> void g(S s, T t){
    std::cout << "f<" << typeid(S).name() << "," << typeid(T).name()
        << ">(" << s << "," << prettify(t) << ")\n";
}


template <typename S, typename T> void f(S s = 0, T t = 0){
    g<S,T>(s,t);
}

template <typename S> void f(S s = 0, double t = 0) {
    g<S,double>(s, t);
}

int main() {
        f(1, 'c');         // f<int,char>(1,'c')
        f(1);              // f<int,double>(1,0)
//        f();               // error: T cannot be deduced
        f<int>();          // f<int,double>(0,0)
        f<int,char>();     // f<int,char>(0,0)
}

The printed output matches the comments for each call to f, and the commented-out call fails to compile as expected.

So I suspect that default template parameters "aren't needed", but probably only in the same sense that default function arguments "aren't needed". As Stroustrup's defect report indicates, the addition of non-deduced parameters was too late for anyone to realise and/or really appreciate that it made defaults useful. So the current situation is in effect based on a version of function templates which was never standard.

Baum answered 15/3, 2010 at 14:46 Comment(3)
@Steve: So the egg was running faster than chicken?:) interesting. Thanks.Crossfade
Probably just one of those things. The C++ standardisation process runs slow in part so that people have time to realise when a change creates opportunities or difficulties elsewhere in the standard. Difficulties hopefully are caught by the people implementing the draft standard as they go along, when they spot a contradiction or ambiguity. Opportunities to allow things that weren't allowed before, rely on someone who wants to write the code noticing that it no longer needs to be illegal...Baum
One more for you: template<typename T = void> int SomeFunction();. The template parameter here is never used, and in fact the function is never called; the only place it is referred to is in a decltype or sizeof. The name deliberately matches the name of another function but the fact it's a template means the compiler will prefer the free function if it exists. The two are used in SFINAE to provide default behaviour where a function definition is missing.Mcchesney
M
2

On Windows, with all versions of Visual Studio you can convert this error (C4519) to a warning or disable it like so:

#ifdef  _MSC_VER
#pragma warning(1 : 4519) // convert error C4519 to warning
// #pragma warning(disable : 4519) // disable error C4519
#endif

See more details here.

Mallarme answered 9/7, 2013 at 8:19 Comment(1)
Note that, while this does disable the "default template arguments are only allowed on a class template" message, it doesn't actually make the template instantiation process use the provided value. That requires VS2013 (or any other compiler that has completed C++11 defect 226 "Default template arguments for function templates")Barfield
D
1

What I use is next trick:

Lets say you want to have function like this:

template <typename E, typename ARR_E = MyArray_t<E> > void doStuff(ARR_E array)
{
    E one(1);
    array.add( one );
}

You will not be allowed, but I do next way:

template <typename T>
struct MyArray_t {
void add(T i) 
{
    // ...
}
};

template <typename E, typename ARR_E = MyArray_t<E> >
class worker {
public:
    /*static - as you wish */ ARR_E* parr_;
    void doStuff(); /* do not make this one static also, MSVC complains */
};

template <typename E, typename ARR_E>
void worker<E, ARR_E>::doStuff()
{
    E one(1);
    parr_->add( one );
}

So this way you may use it like this:

MyArray_t<int> my_array;
worker<int> w;
w.parr_ = &arr;
w.doStuff();

As we can see no need to explicitly set second parameter. Maybe it will be useful for someone.

Diarmid answered 23/7, 2011 at 11:48 Comment(4)
This is definitely not an answer.Wasp
@deadmg - can you explain why? We're not all C++ template gurus. Thanks.Dextrosinistral
This is a workaround which is pretty neat but it won't cover all the cases you might want. For example how would you apply this to a constructor?Transformer
@TiberiuSavin - if I understood you correctly, then you can do like this: template <typename E, typename ARR_E> worker<E, ARR_E>::worker(ARR_E* parr) { parr_ = parr; }. And then use it like this: worker<int> w2(&my_array);Diarmid

© 2022 - 2024 — McMap. All rights reserved.