Tuesday 5 March 2013

SFINAE decltype comma operator trick


Note the decltype statement below contains 2 elements: reserve and bool: decltype(t.reserve(0), bool())

This is a trick using SFINAE and the comma operator: SFINAE will cull the function if 'reserve' doesn't exist and the comma operator will mean the result type of the decltype statement will be a bool.

This means we can easily implement an 'enable_if'esque statement to check for the existence of a member function called 'reserve'

// Culled by SFINAE if reserve does not exist or is not accessible
template <typename T>
constexpr auto has_reserve_method(T& t) -> decltype(t.reserve(0), bool()) { return true; }

// Used as fallback when SFINAE culls the template method
constexpr bool has_reserve_method(...) { return false; }

template <typename T, bool b>
struct Reserver
{
  static void apply(T& t, size_t n) { t.reserve(n); }
};

template <typename T>
struct Reserver <T, false>
{
  static void apply(T& t, size_t n) {}
};

template <typename T>
bool reserve(T& t, size_t n)
{
  Reserver<T, has_reserve_method(t)>::apply(t, n);
  return has_reserve_method(t);
}

(Thanks to Matthieu M for his post on stackoverflow here)

--------------------------

Another implementation which has 2 SFINAE functions to access a member int, items_n or items_c, ultimately falling back to 0 if neither exist

// culled by SFINAE if items_n does not exist
template<typename T>
constexpr auto has_items_n(int) -> decltype(std::declval<T>().items_n, bool())
{
    return true;
}
// catch-all fallback for items with no items_n
template<typename T> constexpr bool has_items_n(...)
{
    return false;
}
//-----------------------------------------------------

template<typename T, bool>
struct GetItemsN
{
    static int value(T& t)
    {
        return t.items_n;
    }
};
template<typename T>
struct GetItemsN<T, false>
{
    static int value(T&)
    {
        return 0;
    }
};
//-----------------------------------------------------

// culled by SFINAE if items_c does not exist
template<typename T>
constexpr auto has_items_c(int) -> decltype(std::declval<T>().items_c, bool())
{
    return true;
}
// catch-all fallback for items with no items_c
template<typename T> constexpr bool has_items_c(...)
{
    return false;
}
//-----------------------------------------------------

template<typename T, bool>
struct GetItemsC
{
    static int value(T& t)
    {
        return t.items_c;
    }
};
template<typename T>
struct GetItemsC<T, false>
{
    static int value(T&)
    {
        return 0;
    }
};
//-----------------------------------------------------

template<typename T>
int get_items(T& t)
{
    if (has_items_n<T>(0))
        return GetItemsN<T, has_items_n<T>(0)>::value(t);
    if (has_items_c<T>(0))
        return GetItemsC<T, has_items_c<T>(0)>::value(t);
    return 0;
}
//-----------------------------------------------------

When you have two candidates function templates, and want to use SFINAE to choose between them, sometimes you may have a parameter for which both overloads will work.

To prevent ambiguity you can favour one overload over the other.

By using implicit type casting we can make one overload a better match, therefore resolving the ambiguity.

#include <iostream>

template<class T>
auto serialize_imp(std::ostream& os, T const& obj, int)
    -> decltype(os << obj, void())
{
    os << obj;
}

template<class T>
auto serialize_imp(std::ostream& os, T const& obj, long)
    -> decltype(obj.stream(os), void())
{
    obj.stream(os);
}

template<class T>
auto serialize(std::ostream& os, T const& obj)
    -> decltype(serialize_imp(os, obj, 0), void())
{
    serialize_imp(os, obj, 0);
}

struct X
{
    void stream(std::ostream&) const
    {
        std::cout << "\nX::stream()\n";
    }
};

int main(){
    serialize(std::cout, 5);
    X x;
    serialize(std::cout, x);
}

Here the ostream operator overload will be chosen when an object with both operator<< and stream() because by passing in 0 for the 3rd parameter of serialize_imp, we choose the overload with the int parameter, as 0 is an int, whereas the long parameter would require an implicit cast.

(Thanks to Xeo for his post on stackoverflow here)

No comments:

Post a Comment