How to work around std::get<>()'s lack of concurency specification

Viewed 318

The following block of code:

  1. Is technically invalid, since std::get<>() is not thread safe. Reference: Is using `std::get<I>` on a `std::tuple` guaranteed to be thread-safe for different values of `I`?

  2. As far as I can tell, is effectively safe on all implentations of std::tuple<> in the wild right now, and the foreseeable future.

    #include <tuple>
    #include <atomic>
    #include <thread>

    // Out of my control
    using data_t = std::tuple<int, int, int, int>;
    void foo(data_t); 
    //

    int main() {
        data_t landing;
        std::atomic<int> completed = 0;

        // Whichever thread pings last will be the one performing foo()
        auto ping = [&](){
            if(++completed == 4) {
                foo(landing);
            }
        };

        std::thread a([&](){ std::get<0>(landing) = 1; ping(); });
        std::thread b([&](){ std::get<1>(landing) = 2; ping(); });
        std::thread c([&](){ std::get<2>(landing) = 3; ping(); });
        std::thread d([&](){ std::get<3>(landing) = 4; ping(); });

        a.join();
        b.join();
        c.join();
        d.join();

        return 0;
    }

To make matters even more fun, the actual code in question is chock-full of variadic templates, so writing a one-shot landing pad struct to handle that one scenario is not going to cut it. It has to be a general solution.

My current options are:

  • Effectively re-implement std::tuple<> with a reworded std::get<> documentation, which is a waste of time and a waste of code.
  • Push a proposal for std::get<>(std::tuple) to provide guarantees similar to std::vector<>, and document the fact that the code is only valid as of a yet unreleased verion of the standard.
  • ignore the issue, and rely on the fact that realistically, this will almost certainly always work.

None of those are particularly great in the short run... So my questions are:

  • Did I miss something that invalidates point #2?
  • Is there a better workaround that would allow the implementation to be technically valid while not having to support an excessive amount of extra code.
  • Any other opinions on the subject are welcome.
2 Answers

Push a proposal for std::get<>(std::tuple) to provide guarantees similar to std::vector<>, and document the fact that the code is only valid as of a yet unreleased version of the the standard.

I think this is the way to go, as it provides value for the entire C++ community and should not be a burden on implementers. It is also an excellent opportunity to write your first proposal.

I suggest doing that, and for now assuming that this will work, even though it is UB. If your software is super-critical (e.g. flight control system) and you want to be 100% sure that you are not relying on something that could break in the future... then implement your own tuple.

template<class...Ts>
std::tuple< std::remove_reference_t<T>* >
to_pointers( std::tuple<Ts...>& );

template<class T0, class...Ts>
std::array<T0, 1+sizeof...(Ts)>
to_array( std::tuple<T0, Ts...> const& );

write these.

int main() {
    data_t landing;
    std::atomic<int> completed = 0;

    // Whichever thread pings last will be the one performing foo()
    auto ping = [&](){
        if(++completed == 4) {
            foo(landing);
        }
    };

    auto arr = to_array(to_pointers(landing));

    std::thread a([&](){ *arr[0] = 1; ping(); });
    std::thread b([&](){ *arr[1] = 2; ping(); });
    std::thread c([&](){ *arr[2] = 3; ping(); });
    std::thread d([&](){ *arr[3] = 4; ping(); });

    a.join();
    b.join();
    c.join();
    d.join();

    return 0;
}

we access the tuple elements via pointers to them instead of via std::get. The problem is in the specification of std::get; once you have the independent pointers to the independent objects guaranteed to exist within the tuple, you are race condition free.

So we convert the tuple to an array of pointers in one thread (which is basically free), then use it in the threads safely.

Related