14

It is like std::optional, but doesn't store an extra bool. User has to make sure to access only after initializing.

template<class T>
union FakeOptional { //Could be a normal struct in which case will need std::aligned storage object.
    FakeOptional(){}  //Does not construct T
    template<class... Args>
    void emplace(Args&&... args){
        new(&t) T{std::forward<Args&&>(args)...};
    }
    void reset(){
        t.~T();
    }
    operator bool() const {
        return true;
    }
    constexpr const T* operator->() const {
        return std::launder(&t);

    }
    constexpr T* operator->() {
        return std::launder(&t);
    }
    T t;
};

If you are wondering why I need such an obscure datastructure, check here: https://gitlab.com/balki/linkedlist/tree/master

Question

  1. Is it ok to ignore std::launder? I guess not.
  2. Since std::launder is available only in c++17, how to implement above class in c++14? boost::optional and std::experimental::optional should have needed similar feature or did they use compiler specific magic?

Note: It is easy to miss, the type is declared as union. Which means constructor of T is really not called. Ref: https://gcc.godbolt.org/z/EVpfSN

balki
  • 26,394
  • 30
  • 105
  • 151
  • You'll be fine with just placement new into array of bytes + `reinterpret_cast`. – user7860670 Jan 18 '19 at 18:59
  • 4
    `operator bool() const { return true; }`... `FakeOptional`. Yep, sounds right. – Barry Jan 18 '19 at 18:59
  • 2
    @FrançoisAndrieux It is a union, not struct. So it is not constructed. That is the whole point of this type. Avoid default construction but just allocate enough so, may be used later. – balki Jan 18 '19 at 19:06
  • 1
    @NicolBolas, I am using an union. Note: `union FakeOptional` – balki Jan 18 '19 at 19:28
  • @VTT wouldn't that give you exactly the same problems around e.g. compiler optimisation of `const` members that `std::launder` is meant to resolve? – Tommy Jan 18 '19 at 19:51
  • 3
    It is completely and utterly a construct for the compiler, you must have compiler support. The best you can do is check if there is `__builtin_launder` or something to that effect. – Passer By Jan 18 '19 at 19:56
  • Problems to be solved with `std::launder` should rarely occur on the first place. Once you un / re initialize class instance pointers and references to old one should not be used. When using union accessing currently active object should be fine and initialization through placement new is a recommended (in standard) practice. – user7860670 Jan 18 '19 at 20:07
  • @VTT, So you are saying, I I just need to do this and not worry about `launder`? `constexpr T* operator->() {return &t;}` – balki Jan 18 '19 at 20:14
  • I don't think you even need to bother with a manually written operator, just `&obj.t` should be fine. – user7860670 Jan 18 '19 at 20:16
  • As usual, some volatile should do the trick. (I can't post that as an answer.) – curiousguy Jan 19 '19 at 08:51

1 Answers1

4

No, you can't. One of the reasons that std::launder is proposed is that std::optional is not implementable in C++14. You can refer to this discussion for detail.

On the other hand, you can implement one without constexpr. The idea is to use a buffer with reinterpret_cast because the result of reinterpret_cast will always refer to the newly created object (in C++17 std::launder is still required but in C++14 this is fine). For example,

template<class T>
struct FakeOptional { 
    FakeOptional(){}  
    template<class... Args>
    void emplace(Args&&... args){
        new(&storage) T{std::forward<Args&&>(args)...};
    }
    void reset(){
        reinterpret_cast<T*>(&storage)->~T();
    }
    operator bool() const {
        return true;
    }
    const T* operator->() const {
        return reinterpret_cast<const T*>(&storage);
    }
    T* operator->() {
        return reinterpret_cast<T*>(&storage);
    }
    std::aligned_storage_t<sizeof(T), alignof(T)> storage;
};

The implementation of boost::optional uses this idea and does not implement constexpr semantic (you can refer to its source code for details).

alfC
  • 14,261
  • 4
  • 67
  • 118
xskxzr
  • 12,442
  • 12
  • 37
  • 77
  • 1
    I wonder if, with nowadays compilers, `std::launder` is necessary. In this [compiler explorer page](https://godbolt.org/z/b_VPQZ), neither Clang, GCC, MSVC or ICC optimize the code assuming that a const member is not changed accross a function call. So that one could induce that `std::launder` is not necessary. But one cannot make a law from one example. Do you know an example case where the compiler does the optimization? That would prove that your approach is necessary and the weakness of the previous inductive reasoning. – Oliv Jan 19 '19 at 08:41
  • But but but pointers are "trivial types" officially, so a byte wise copy of the pointer should do! Or trivial types aren't really "trivial". – curiousguy Jan 19 '19 at 09:01
  • 2
    @Oliv My approach is only necessary in a language-lawyer fashion, of course. The restriction for const/ref members [is added in C++03](http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#89), so some old code may break if such optimization is enabled. This is one of the reasons why these compilers are kind of conservative, I guess. – xskxzr Jan 19 '19 at 10:32
  • @curiousguy Sorry I don't get your point. Why do you mention pointer copy? – xskxzr Jan 19 '19 at 10:34
  • @curiousguy The problem is not "how" an object is copied, but whether the compiler can assume that an object value has changed or not. Consider a function that take argument by const reference. In this case the compiler can assume the object value has is not changed by the call and perform code optimization based on this assumption. But what happens when a function takes an argument by non-const reference and that argument is a non const object that has a const member? Shall the compiler assumes the const member has not changed accross the call? According to the standard, the answer is yes. – Oliv Jan 19 '19 at 11:08
  • @xskxzr What does `std::launder`, except a copy of an official trivial scalar type? – curiousguy Jan 19 '19 at 11:53
  • @Oliv "_In this case the compiler can assume the object value has is not changed by the call and perform code optimization based on this assumption_" Not if there is a `const_cast`! – curiousguy Jan 19 '19 at 11:54
  • 1
    @curiousguy Ohh true, I had forgotten that const reference are not an interface contract in c++ :((! But const object (including member suboject) is a contract because it is forbidden to change the value of a const object. – Oliv Jan 19 '19 at 12:28
  • @xskxzr I will remove the `constexpr` tag. But, is using `aligned_storage` necessary or the `union` method is good enough? I find `union` less hacky than `reinterpret_cast` – balki Jan 19 '19 at 18:12
  • @balki No, your method can't get the newly created object if `T` has a const/ref member. – xskxzr Jan 19 '19 at 18:40
  • @balki The rule is that you can't break a language invariant (like const means constant) by reusing storage or other means (like naming a const variable during its own construction). (OTOH you can break a class user invariant of constness, like a private variable that is constant throughout the lifetime of the object by virtue of having no way to change it, by recreating another object of the same type.) – curiousguy Jan 22 '19 at 03:16