Question

The code below is a minimal example of my problem. I created a simple template class containing a fixed-size array, and overloaded the assignment operator to accept any class defining the methods size() and begin() (eg, initializer_lists). I don't understand why g++ is not able to resolve my call to this operator (I'm using gcc 4.6):

***.cpp: In function ‘int main()’:
***.cpp:46:22: error: no match for ‘operator=’ in ‘a = {42, -1.0e+0, 3.14158999999999988261834005243144929409027099609375e+0}’
***.cpp:46:22: note: candidates are:
***.cpp:23:8: note: template<class U> A<T, N>::self& A::operator=(const U&) [with U = U, T = double, unsigned int N = 3u, A<T, N>::self = A<double, 3u>]
***.cpp:8:7: note: A<double, 3u>& A<double, 3u>::operator=(const A<double, 3u>&)
***.cpp:8:7: note:   no known conversion for argument 1 from ‘<brace-enclosed initialiser list>’ to ‘const A<double, 3u>&’
***.cpp:8:7: note: A<double, 3u>& A<double, 3u>::operator=(A<double, 3u>&&)
***.cpp:8:7: note:   no known conversion for argument 1 from ‘<brace-enclosed initialiser list>’ to ‘A<double, 3u>&&’

The first candidate is listed correctly, but there is no error message associated. Here is the code:

#include <iostream>
#include <algorithm>
#include <initializer_list>

// ------------------------------------------------------------------------

template <typename T, unsigned N>
class A
{
public:

    typedef A<T,N> self;

    // Default ctor
    A() {}

    // Copy ctor
    template <typename U>
    A( const U& other ) { operator=(other); }

    // Assignemnt
    template <typename U>
    self& operator= ( const U& other )
    {
        if ( other.size() == N )
            std::copy_n( other.begin(), N, m_data );
            return *this;
    }

    // Display contents
    void print() const
    {
        for ( unsigned i = 0; i < N; ++i )
            std::cout << m_data[i] << " ";
        std::cout << std::endl;
    }

private:
    T m_data[N];
};

// ------------------------------------------------------------------------

int main()
{
    A<double,3> a;
    a = {42,-1.0,3.14159};
    a.print();
}

Does anyone know why this might be ambiguous, or what I did wrong?


Note: Ideally, I would even like to replace the first two lines of my main by a single one A<double,3> a = {42,-1.0,3.14159}; but I'm not sure how, I currently get the following error:

***: In function ‘int main()’:
***:45:34: error: could not convert ‘{42, -1.0e+0, 3.14158999999999988261834005243144929409027099609375e+0}’ from ‘<brace-enclosed initialiser list>’ to ‘A<double, 3u>’
Was it helpful?

Solution

Unlike auto, where a braced-init-list is deduced as an initializer_list, template argument deduction considers it to be a non-deduced context, unless there exists a corresponding parameter of type initializer_list<T>, in which case the T can be deduced.

From §14.8.2.1/1 [temp.deduct.call] (emphasis added)

Template argument deduction is done by comparing each function template parameter type (call it P) with the type of the corresponding argument of the call (call it A) as described below. If removing references and cv-qualifiers from P gives std::initializer_list<P0> for some P0 and the argument is an initializer list (8.5.4), then deduction is performed instead for each element of the initializer list, taking P0 as a function template parameter type and the initializer element as its argument. Otherwise, an initializer list argument causes the parameter to be considered a non-deduced context (14.8.2.5).

Thus the argument to your operator= is not deduced to be an initializer_list<double>. For the code to work, you must define an operator= that takes an initializer_list argument.

template <typename U>
self& operator= ( const std::initializer_list<T>& other )
{
    if ( other.size() == N )
        std::copy_n( other.begin(), N, m_data );
    return *this;
}

OTHER TIPS

A brace-enclosed initializer list does not necessarily have the type std::initializer_list<T>, so you need to specify that the assignment operator template expects an std::initializer_list:

  template <typename U>
  A& operator=(std::initializer_list<U> other )
  {
    if ( other.size() == N )
      std::copy_n( other.begin(), N, m_data );
    return *this;
  }

or

  A& operator=(std::initializer_list<double> other )
  {
    if ( other.size() == N )
      std::copy_n( other.begin(), N, m_data );
    return *this;
  }

I must say, an assignment operator that silently fails if the sizes don't match doesn't seem like a great idea.

I'd say it's this:

A<double,3> a;
a = {42,-1.0,3.14159};

You are initializing a with default constructor, and then trying to use initializer list on already initialized object - which complains about lacking of appropriate operator= overload. Instead try:

A<double,3> a = {42,-1.0,3.14159};

EDIT:

You also didn't defined required constructor:

template <typename T, unsigned N>
class A
{
public:
    A(std::initializer_list list) : m_data(list) {}

//...
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top