简体   繁体   中英

How to use std::atomic<T>::is_always_lock_free for SFINAE?

How can I use std::atomic<T>::is_always_lock_free with SFINAE? I have a class template MyClass<T> and I would like to switch between 2 implementations, depending on whether std::atomic<T>::is_always_lock_free is true . This is what I have:

template<typename T, typename Enable = void>
class MyClass {
// Fallback implementation
};

template<typename T>
class MyClass<T, typename std::enable_if<std::atomic<T>::is_always_lock_free>::type> {
// Optimized implementation using std::atomic
};

Now, when I try to create an instance of MyClass<SomeCustomClass> , I get a compiler error:

_Atomic cannot be applied to type 'SomeCustomClass' which is not trivially copyable

It tries to use the template specialization, but instead of using the fallback implementation, it doesn't compile at all. Could someone kindly explain what's wrong here? And how do I get the desired result?

You need to delay checking std::atomic<T>::is_always_lock_free until after you know that T is trivially copyable. Otherwise, atomic<T> is ill-formed.

For that, there's std::conjunction - which is lazy / short-circuits:

template <typename T>
struct is_lock_free_impl
: std::integral_constant<bool, std::atomic<T>::is_always_lock_free> { };

template <typename T>
using is_lock_free = std::conjunction<
    std::is_trivially_copyable<T>,
    is_lock_free_impl<T>>;

Now, this trait will abort early and yield false_type if T isn't trivially copyable. And if it is trivially copyable, then it's valid to instantiate atomic<T> , so then we check that trait.

In the expression std::atomic<T>::is_always_lock_free the instantation of std::atomic<T> fails (for the non-triviably copyable types), and that's not immediate context, hence the compilation fails. You need to not instantiate std::atomic<T> in that case. SFINAE would have worked here if std::atomic<T> was legal, but std::atomic<T>::is_always_lock_free would not be.

So how do I get the desired result?

With a custom trait:

#include <atomic>
#include <array>
#include <iostream>

class SomeClass { SomeClass& operator=(const SomeClass&) { return *this; } };

template<typename... T>
struct make_void { typedef void type; };

template<typename... T>
using void_t = typename make_void<T...>::type;

template<typename T, typename = void>
struct IsAlwaysLockFree
{
    static constexpr bool value = false;
};

template<typename T>
struct IsAlwaysLockFree<T, void_t<typename std::enable_if<!std::is_trivially_copyable<T>::value>::type>>
{
    static constexpr bool value = false;
};

template<typename T>
struct IsAlwaysLockFree<T, void_t<typename std::enable_if<std::is_trivially_copyable<T>::value>::type>>
{
    static constexpr bool value = std::atomic<T>::is_always_lock_free;
};

template<typename T, typename Enable = void>
class MyClass {
// Fallback implementation
public:
    MyClass(){ std::cout << "Fallback\n"; }
};

template<typename T>
class MyClass<T, typename std::enable_if<IsAlwaysLockFree<T>::value>::type> {
// Optimized implementation using std::atomic
public:
    MyClass(){ std::cout << "Optimized\n"; }
};

int main()
{
    MyClass<SomeClass> a;
    MyClass<std::array<int, 1024>> b;
    MyClass<int> c;
}

Live on Coliru

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM