TartanLlama / expected

C++11/14/17 std::expected with functional-style extensions

Home Page:https://tl.tartanllama.xyz

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Use of placement new in expected_operations_base.

swan-gh opened this issue · comments

commented

Hello TL :)

This might be a non-issue, it's likely I've overlooked something, but it would appear that the usage of placement new here:

template <class... Args> void construct(Args &&... args) noexcept {
    new (std::addressof(this->m_val)) T(std::forward<Args>(args)...);
    this->m_has_val = true;
  }

  template <class Rhs> void construct_with(Rhs &&rhs) noexcept {
    new (std::addressof(this->m_val)) T(std::forward<Rhs>(rhs).get());
    this->m_has_val = true;
  }

begins to misbehave in some cases when constructing an expected<T const> from an expected<T>. std::addressof returns a pointer of type T const * which seems to confuse the overload resolution of operator new. E.g.

#include "expected.hpp"
#include <iostream>

class A
{
public:
    A() = default;;
    A(A const &) = default;
    A(A&&) = default;
    ~A() {}

    A& operator=(A const &) = default;
    A& operator=(A &&) = default;

    int i = 5u;
};

template< typename T>
using Expected = tl::expected<T, int>;

Expected<A> getSomeExpected()
{
    return Expected<A>(A{});
}

int main()
{	
    Expected<A const> a (getSomeExpected());
}

Gives the following:

1> error C2665: 'operator new': none of the 4 overloads could convert all the argument types
...
1> expectedtest\expected.hpp(665):  note: while trying to match the argument list '(unsigned __int64, _Ty *)'
1>        with
1>        [
1>            _Ty=const A
1>        ]

Casting the address type passed to placement new works, although I suspect that's probably UB.
Compiled with:
MSVC++ 14.16, _MSC_VER == 1916 (Visual Studio 2017 version 15.9)
/std:c++17

commented

I think the solution might be to use something similar to std::construct_at from C++20?
https://en.cppreference.com/w/cpp/memory/construct_at
equivalent to

return ::new (const_cast<void*>(static_cast<const volatile void*>(p)))
    T(std::forward<Args>(args)...);

Hey just bumping this because I ran into a similar issue, this time with using expected to store types with a deleted placement new operator. In my case this works fine in MSVC with C++23 std::expected, and I see that they indeed use std::construct_at for all their member construction.