Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
258 views
in Technique[技术] by (71.8m points)

c++ - Templates don't always guess initializer list types

#include <initializer_list>
#include <utility>

void foo(std::initializer_list<std::pair<int,int>>) {}
template <class T> void bar(T) {}

int main() {
    foo({{0,1}});  //This works
    foo({{0,1},{1,2}});  //This works
    bar({{0,1}});  //This warns
    bar({{0,1},{1,2}});  //This fails
    bar(std::initializer_list<std::pair<int,int>>({{0,1},{1,2}}));  //This works
}

This doesn't compile in gcc 4.5.3, it gives a warning for the marked line saying deducing ‘T’ as ‘std::initializer_list<std::initializer_list<int> >’ and an error for the marked line saying no matching function for call to ‘bar(<brace-enclosed initializer list>)’. Why can gcc deduce the type of the first call to bar but not the second, and is there a way to fix this other than long and ugly casting?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

GCC according to C++11 cannot deduce the type for either first two calls to bar. It warns because it implements an extension to C++11.

The Standard says that when a function argument in a call to a function template is a { ... } and the parameter is not initializer_list<X> (optionally a reference parameter), that then the parameter's type cannot be deduced by the {...}. If the parameter is such a initializer_list<X>, then the elements of the initializer list are deduced independently by comparing against X, and each of the deductions of the elements have to match.

template<typename T>
void f(initializer_list<T>);

int main() {
  f({1, 2}); // OK
  f({1, {2}}); // OK
  f({{1}, {2}}); // NOT OK
  f({1, 2.0}); // NOT OK
}

In this example, the first is OK, and the second is OK too because the first element yields type int, and the second element compares {2} against T - this deduction cannot yield a constradiction since it doesn't deduce anything, hence eventually the second call takes T as int. The third cannot deduce T by any element, hence is NOT OK. The last call yields contradicting deductions for two elements.

One way to make this work is to use such a type as parameter type

template <class T> void bar(std::initializer_list<std::initializer_list<T>> x) {
  // ...
}

I should note that doing std::initializer_list<U>({...}) is dangerous - better remove those (...) around the braces. In your case it happens to work by accident, but consider

std::initializer_list<int> v({1, 2, 3});
// oops, now 'v' contains dangling pointers - the backing data array is dead!

The reason is that ({1, 2, 3}) calls the copy/move constructor of initializer_list<int> passing it a temporary initializer_list<int> associated with the {1, 2, 3}. That temporary object will then be destroyed and die when the initialization is finished. When that temporary object that is associated with the list dies, the backing-up array holding the data will be destroyed too (if the move is elided, it will live as long as "v"; that's bad, since it would not even behave bad guaranteedly!). By omitting the parens, v is directly associated with the list, and the backing array data is destroyed only when v is destroyed.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...