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

1

18

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.

Apportion answered 31/1, 2017 at 11:42 Comment(6)
[dcl.spec.auto]/11 "If the type of an entity with an undeduced placeholder type is needed to determine the type of an expression, the program is ill-formed." So I think your first program is ill-formed. (The standard doesn't say "no diagnostic required" so there should be a diagnostic).Confection
Fairly certain this falls under the requirement that the compiler only considers functions that were defined before the template it is trying to instantiate. Could still be a bug as I am not 100% sure on the rules.Wernher
I guess we could argue about whether the use of _T2 in pair::operator= instantiation counts as "determine the type of an expression"Confection
If there isn't wording saying that this kind of shenanigans is ill-formed NDR, there should be.Annelid
Maybe related: LWG2729Unexpressive
This is another variant of LWG2452, I think.Annelid
A
9

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.)

Annelid answered 31/1, 2017 at 20:13 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.