SFINAE issue with traits detecting division

176 views Asked by At

I have the following traits to detect if two type are divisible, and return the resulting operation type or another Default type otherwise:

struct default_t { };

// Overloaded template for fallbacks
template <class U, class V>
default_t operator/(U, V);

// If the expression std::declval<U>()/std::declval<V>() is valid,
// gives the return type, otherwize provide Default.
template <class U, class V, class Default>
struct div_type_or {
    using type_ = decltype(std::declval<U>() / std::declval<V>());
    using type = typename std::conditional<
        std::is_same<type_, default_t>{},
            Default,
            type_>::type;
};

template <class... Args>
using div_type_or_t = typename div_type_or<Args...>::type;

This works well with libstdc++, but not with libc++, when I try with a non-divisible std::chrono::duration type, e.g.:

struct A { };

div_type_or_t<std::chrono::seconds, A, int> b;

I get the following error:

/Library/Developer/CommandLineTools/usr/include/c++/v1/chrono:764:81: error: no type named 'type' in 'std::__1::common_type' typename common_type::type>::value> ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~

/Library/Developer/CommandLineTools/usr/include/c++/v1/chrono:777:7: note: in instantiation of default argument for '__duration_divide_imp >, A>' required here : __duration_divide_imp, _Rep2> ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ >

/Library/Developer/CommandLineTools/usr/include/c++/v1/chrono:784:10: note: in instantiation of template class 'std::__1::chrono::__duration_divide_result >, A, false>' requested here typename __duration_divide_result, _Rep2>::type ^

test.cpp:16:46: note: while substituting deduced template arguments into function template 'operator/' [with _Rep1 = long long, _Period = std::__1::ratio<1, 1>, Rep2 = A] using type = decltype(std::declval() / std::declval()); ^

test.cpp:24:1: note: in instantiation of template class 'div_type_or >, A, D>' requested here using div_type_or_t = typename div_type_or::type; ^

test.cpp:48:19: note: in instantiation of template type alias 'div_type_or_t' requested here div_type_or_t, D>{}, "");

As I understand this, it is because the following overload of operator/ for std::chrono::duration fails:

template< class Rep1, class Period, class Rep2 >
duration<typename std::common_type<Rep1,Rep2>::type, Period>
    constexpr operator/( const duration<Rep1, Period>& d,
                         const Rep2& s );

I thought that since the std::common_type is part of the function signature, this would allow SFINAE to work and use my custom overload of operator/ and thus deduce default_t, but apparently this does not work...

Since this works with libstdc++, I just wanted to know if there was an issue with my code (maybe I did not understand the way SFINAE would work in this case) or if this was a libc++ bug?

1

There are 1 answers

2
Jarod42 On BEST ANSWER

In libc++, we have:

template <class _Duration, class _Rep, bool = __is_duration<_Rep>::value>
struct __duration_divide_result
{
};

template <class _Duration, class _Rep2,
    bool = is_convertible<_Rep2,
                          typename common_type<typename _Duration::rep, _Rep2>::type>::value>
struct __duration_divide_imp
{
};

template <class _Rep1, class _Period, class _Rep2>
struct __duration_divide_imp<duration<_Rep1, _Period>, _Rep2, true>
{
    typedef duration<typename common_type<_Rep1, _Rep2>::type, _Period> type;
};

template <class _Rep1, class _Period, class _Rep2>
struct __duration_divide_result<duration<_Rep1, _Period>, _Rep2, false>
    : __duration_divide_imp<duration<_Rep1, _Period>, _Rep2>
{
};

template <class _Rep1, class _Period, class _Rep2>
inline _LIBCPP_INLINE_VISIBILITY
_LIBCPP_CONSTEXPR
typename __duration_divide_result<duration<_Rep1, _Period>, _Rep2>::type
operator/(const duration<_Rep1, _Period>& __d, const _Rep2& __s)
{
    typedef typename common_type<_Rep1, _Rep2>::type _Cr;
    typedef duration<_Cr, _Period> _Cd;
    return _Cd(_Cd(__d).count() / static_cast<_Cr>(__s));
}

So instead of std::common_type<_Rep1, _Rep2>::type,
they use __duration_divide_result<duration<_Rep1, _Period>, _Rep2 /*, false*/>,
which instantiates __duration_divide_imp<duration<_Rep1, _Period>, _Rep2 /*, is_convertible<_Rep2, typename common_type<_Rep1, _Rep2>::type>::value*/>

That usage is an hard error.

I would say that implementation in libc++ is incorrect in that regard.