Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

`pair::operator=(pair&&)` error with `auto&` deduced move operations - libstdc++ regression?

Given this program:

struct Val
{
    Val() = default;
    Val(Val&&) = default;
    auto& operator=(Val&&);
};

/* PLACEHOLDER */

auto& Val::operator=(Val&&) { return *this; }   

Substituting /* PLACEHOLDER */ with...

int main()
{
    std::vector<std::pair<int, Val>> v;
    v.emplace(std::begin(v), 0, Val{});
}

...compiles successfully on:

  • g++ 6.2.0
  • g++ 6.3.0
  • g++ 7.0.1 (trunk)

  • clang++ 3.9.1

  • clang++ 5.0.0 (HEAD)

on wandbox


Substituting /* PLACEHOLDER */ with...

template <typename TVec>
void a(TVec& v)
{
    v.emplace(std::begin(v), 0, Val{});
}

int main()
{
    std::vector<std::pair<int, Val>> v;
    a(v);
}

...compiles successfully on:

  • g++ 6.2.0
  • clang++ 3.9.1

...but produces a compile-time error on:

  • g++ 6.3.0
  • g++ 7.0.1 (trunk)
  • clang++ 5.0.0 (HEAD)

on wandbox


The produced error seems to be related to a constrained pair operator=(pair&&) overload - from include/bits/stl_pair.h on GitHub's libstdc++ mirror:

  pair&
  operator=(typename conditional<
    __and_<is_move_assignable<_T1>,
           is_move_assignable<_T2>>::value,
    pair&&, __nonesuch&&>::type __p)
  noexcept(__and_<is_nothrow_move_assignable<_T1>,
              is_nothrow_move_assignable<_T2>>::value)
  {
first = std::forward<first_type>(__p.first);
second = std::forward<second_type>(__p.second);
return *this;
  }
  • Substituting is_move_assignable<_T2> with std::true_type allows the code to compile.

  • Moving the definition of Val::operator=(Val&&) before /* PLACEHOLDER */ allows the code to compile.

  • Changing auto& Val::operator=(Val&&) to Val& Val::operator=(Val&&) allows the code to compile.

What is going on here? Is this an implementation defect in the latest version of libstdc++? Or were the older versions incorrectly compiling ill-formed code?


EDIT: as AndyG discovered in his (now deleted) answer, the error also occurs when a call to an empty function is made before invoking emplace:

template <typename TVec>
void a(TVec&) { }

int main()
{
    std::vector<std::pair<int, Val>> v;
    a(v);
    v.emplace(std::begin(v), 0, Val{});
}

Commeting out a(v); above prevents the compile-time error from being produced. This behavior is present both in g++7 and clang++5.

on wandbox


Another weird case was discovered by Sergey Murzin, and can be tested out on wandbox:

int main()
{
    std::vector<std::pair<int, Val>> v;
    v.emplace(v.begin(), 0, Val{});
    std::cout << v.back().first << std::endl;
}

The code above produces a compiler error. Commenting out the line containing std::cout prevents the error from occurring. This behavior is present both in g++7 and clang++5.

like image 788
Vittorio Romeo Avatar asked Jan 31 '17 11:01

Vittorio Romeo


People also ask

Does std :: pair have operator?

Yes. operator<() is defined for std::pair<T1, T2> , assuming that both T1 and T2 are themselves comparable.

What is a std :: pair?

std::pair is a class template that provides a way to store two heterogeneous objects as a single unit. A pair is a specific case of a std::tuple with two elements.

How do I make a pair pair in C++?

For pair, use the not equal (!=) operator: The!= operator compares the first values of two sets, say pair1 and pair2, i.e. if pair1 and pair2 are given, the!= operator compares the first values of those two pairs.

How do you compare STD pairs?

std::relational operators (pair) Two pair objects compare equal to each other if both their first members compare equal to each other and both their second members compare also equal to each other (in both cases using operator== for the comparison).


1 Answers

This is almost certainly a point-of-instantiation issue. If you do something that triggers the instantiation of pair<int, Val>'s definition in main, then you get the error. Otherwise, it's only instantiated when vector::emplace is instantiated, which the implementations at issue here defer to the end of the translation unit (which is allowed, see [temp.point]/8), at which point the assignment operator is fully defined and callable.

a(v) triggers the instantiation of the definition of pair<int, Val> because it's needed for ADL. If you write ::a(v) or (a)(v) (both suppress ADL) then the error disappears. (Obviously, a.back().first requires the instantiation of pair<int, Val>: you are accessing its data member.)

like image 77
T.C. Avatar answered Oct 04 '22 02:10

T.C.