2

I'm trying to create an addition operator for a custom template class, where the first argument is allowed to be either an instance of my class or a basic numeric type. My operator has a definition similar to the example code below:

#include <type_traits>

template<typename T>
struct MyTemplateStruct {
    T val;
};

template<typename T, typename U>
struct MyCommonType {
    typedef std::common_type_t<T, U> type;
};
template<typename T, typename U>
using MyCommonTypeT = typename MyCommonType<T, U>::type;

template<typename T, typename U>
MyTemplateStruct<MyCommonTypeT<T, U>> operator +(
    MyTemplateStruct<T> const& a, MyTemplateStruct<U> const& b)
{
    return { a.val + b.val };
}

template<typename T, typename U>
MyTemplateStruct<MyCommonTypeT<T, U>> operator +(
    T const a, MyTemplateStruct<U> const& b)
{
    return { a + b.val };
}

int main()
{
    MyTemplateStruct<double> a{ 0 }, b{ 0 };
    a = a + b;

    return 0;
}

My expectation was that due to SFINAE, the compilation error that results from trying to instantiate the second operator definition with T = MyTemplateStruct<double>, U = double would just exclude that template from the list of potential matches. However, when I compile, I'm getting the following error:

/usr/include/c++/7/type_traits: In substitution of ‘template<class ... _Tp> using common_type_t = typename std::common_type::type [with _Tp = {MyTemplateStruct, double}]’:
main.cpp:18:38: required from ‘struct MyCommonType<MyTemplateStruct, double>’
main.cpp:31:39: required by substitution of ‘template<class T, class U> MyTemplateStruct<typename MyCommonType<T, U>::type> operator+(T, const MyTemplateStruct&) [with T = MyTemplateStruct; U = double]’
main.cpp:40:13: required from here /usr/include/c++/7/type_traits:2484:61: error: no type named ‘type’ in ‘struct std::common_type, double>’

If I directly utilize std::common_type_t in the operator definition, instead of using my wrapper template MyCommonTypeT, then SFINAE works as I expect and there is no error. How can I make the above code compile when I have to wrap the call to std::common_type in another template?

Jeff G
  • 4,470
  • 2
  • 41
  • 76
  • Looking at [this answer](https://stackoverflow.com/questions/56550980/whats-wrong-with-my-sfinae-testing-supported-operators?rq=1), it appears I may need to add a template that takes the value `true_type`/`false_type` depending on whether my template accepts the provided template parameters. Then, declare my struct to take a defaulted parameter indicating whether the types T/U are supported, and only provide the `typedef` when they are. Seems kind of kludgy, but it may work. – Jeff G Sep 15 '21 at 05:53

1 Answers1

3

You need to make MyCommonTypeT<T, U> (i.e. MyCommonType<T, U>::type) itself invalid, it's not enough that std::common_type_t<T, U> is invalid when declaring type in MyCommonType. For example you can specialize MyCommonType, when MyTemplateStruct is specified as template argument, type is not declared.

template<typename T, typename U>
struct MyCommonType {
    typedef std::common_type_t<T, U> type;
};
template<typename T, typename U>
struct MyCommonType<MyTemplateStruct<T>, U> {};
template<typename T, typename U>
struct MyCommonType<T, MyTemplateStruct<U>> {};

LIVE

songyuanyao
  • 169,198
  • 16
  • 310
  • 405
  • By doing this, I have to explicitly disable the template for each type I want to use it with. Is there a way to instead enable it only when types `T` and `U` pass `std::is_arithmetic_v && std::is_arithmetic_v`? I've seen code where people just add a defaulted template parameter, but didn't know if that is the best way forward. E.g., `template && std::is_arithmetic_v>` that is empty, then overload it when the third parameter is true with the `typedef`. – Jeff G Sep 15 '21 at 06:00
  • 1
    @JeffG https://wandbox.org/permlink/LvUusJcqTuThmAmW – songyuanyao Sep 15 '21 at 06:08
  • I did like that you moved the default parameter into the template specialization, so users can't lie about whether they are numeric. However, is there a reason that you used the `typename` version instead of `bool`? I find the typename version more difficult to read. E.g., what is the advantage over `template struct MyCommonType {};`, then `template struct MyCommonType && std::is_arithmetic_v> { typedef std::common_type_t type; };`? – Jeff G Sep 15 '21 at 06:44
  • Asked and answered; the version I proposed in my previous comment doesn't compile. – Jeff G Sep 15 '21 at 06:48
  • @JeffG It seems the type of specialized non-type template argument can't depend on a template parameter. – songyuanyao Sep 15 '21 at 06:53