6

I'm facing following problem:

I have some generic container, that is able to do some operations on types. The operations are, for simplicity, thread safe, when requested to. And, requested to means that the type in container has typedef std::true_type needs_thread_safety;.

struct thread_safe_item {
    typedef std::true_type needs_thread_safety;
    /* */ 
};

struct thread_unsafe_item {
    typedef std::false_type needs_thread_safety;
    /* */
};
template<typename TItem> container {
    /* some algorithms, that are std::enable_if selected, according to needs_thread_safety */
};

But, I want the needs_thread_safety to be opt-in, and not needed to be defined (= default false_type). I've tried following:

    struct thread_unsafe_item {
        /* */ 
    };

template<typename TItem>
struct thread_safety_selector
{
    template<typename T>
    struct has_defined_thread_safety
    {
        typedef char yes[1];
        typedef char no[2];

        template <typename C> static yes& test(typename C::needs_thread_safety*);
        template <typename> static no& test(...);

        static const bool value = sizeof(test<T>(0)) == sizeof(yes);
    };

    typedef 
        typename std::conditional<
            has_defined_thread_safety<TItem>::value,
            typename TItem::needs_thread_safety,
            std::false_type
        >::type needs_thread_safety;
};
....
struct <typename TItem> container {
    /* changed all TItem::needs_thread_safety selectors to thread_safety_selector<TItem>::needs_thread_safety */
};

But there is apparently no lazy-evaluation going on, as the error is error C2039: 'needs_thread_safety' : is not a member of 'thread_unsafe_item'.

How can I achieve default value for the argument not specified?

It is for educational purposes, so I don't need different way how to solve this problem.

Thanks!

Rost
  • 8,779
  • 28
  • 50
nothrow
  • 15,882
  • 9
  • 57
  • 104
  • Usually I'm using type selectors to specify a particular base class of my final class, where the bases contain the appropriate implementations. – πάντα ῥεῖ Feb 06 '14 at 12:19

3 Answers3

7

You can't use std::conditional for that, as that will always parse all of its arguments. You can create your own predicate:

template <bool, class>
struct get_thread_safety;

template <class T>
struct get_thread_safety<true, T>
{
  typedef typename T::needs_thread_safety type;
};

template <class T>
struct get_thread_safety<false, T>
{
  typedef std::false_type type;
};

// Used like this:

typedef 
    typename get_thread_safety<
        has_defined_thread_safety<TItem>::value,
        TItem
    >::type needs_thread_safety;
Angew is no longer proud of SO
  • 167,307
  • 17
  • 350
  • 455
2

Actually this can be done a bit easier using std::enable_if coupled with std::conditional:

#include <iostream>
#include <type_traits>

// Classes to be checked against needs_thread_safety
struct A {};
struct B { typedef std::true_type needs_thread_safety; };
struct C { typedef std::false_type needs_thread_safety; };

// Checker helper
template<class T> class get_thread_safety
{
    typedef char(&zero_size_t)[0];

    template <class X> static typename std::enable_if<X::needs_thread_safety::value, char>::type check(int);
    template <class X> static typename std::enable_if<!X::needs_thread_safety::value, zero_size_t>::type check(int);
    template <class X> static zero_size_t check(...);

public:
    typedef typename std::conditional<sizeof(check<T>(0)), std::true_type, std::false_type>::type type;
};

int main()
{
   // Usage. Will print 0 1 0
   std::cout << get_thread_safety<A>::type::value << std::endl;     
   std::cout << get_thread_safety<B>::type::value << std::endl;
   std::cout << get_thread_safety<C>::type::value << std::endl; 

   return 0;
}
Rost
  • 8,779
  • 28
  • 50
1

You can actually use std::conditional in a lazy way, you just need to avoid to use the nested typedef inside its template parameter list. You'll also need another fallback type for the false case ("default value", as you called it):

template<typename TItem>
struct thread_safety_selector
{
    template<typename T>
    struct has_defined_thread_safety
    {
        typedef char yes[1];
        typedef char no[2];

        template <typename C> static yes& test(typename C::needs_thread_safety*);
        template <typename> static no& test(...);

        static const bool value = sizeof(test<T>(0)) == sizeof(yes);
    };

    // this would be your "default value"
    struct not_thread_safe {
        typedef std::false_type needs_thread_safety;
    };

    typedef
        typename std::conditional<
            has_defined_thread_safety<TItem>::value,
            TItem,   // <---- note, not using the typedef here
            not_thread_safe
        >::type::needs_thread_safety  needs_thread_safety;
     //  ^^^^^^^^^^^^^^^^^^^^^^^^^^^
     //  lazily, after the selection is done
};
jrok
  • 54,456
  • 9
  • 109
  • 141
  • 1
    Instead of `yes` and `no` (and the `sizeof`), you can use `std::true_type` and `std::false_type` as return types (and then, `decltype(test(0))::value` if necessary). – dyp Feb 14 '14 at 13:45