isocpp / CppCoreGuidelines

The C++ Core Guidelines are a set of tried-and-true guidelines, rules, and best practices about coding in C++
http://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines
Other
42.71k stars 5.44k forks source link

T.65 Is tag dispatch antiquated? #2108

Open Eisenwave opened 1 year ago

Eisenwave commented 1 year ago

In modern C++, I don't see a reason to use tag dispatch in the example of T.65:

Tag Dispatch

struct pod_tag {};
struct non_pod_tag {};

template<class T> struct copy_trait { using tag = non_pod_tag; };   // T is not "plain old data"

template<> struct copy_trait<int> { using tag = pod_tag; };         // int is "plain old data"

template<class Iter>
Out copy_helper(Iter first, Iter last, Iter out, pod_tag)
{
    // use memmove
}

template<class Iter>
Out copy_helper(Iter first, Iter last, Iter out, non_pod_tag)
{
    // use loop calling copy constructors
}

template<class Iter>
Out copy(Iter first, Iter last, Iter out)
{
    return copy_helper(first, last, out, typename copy_trait<Value_type<Iter>>::tag{})
}

constepxr if (Strawman)

struct pod_tag {};
struct non_pod_tag {};

template<class T> struct copy_trait { using tag = non_pod_tag; };   // T is not "plain old data"

template<> struct copy_trait<int> { using tag = pod_tag; };         // int is "plain old data"

template<class Iter>
Out trivially_copy(Iter first, Iter last, Iter out)
{
    // use memmove
}

template<class Iter>
Out algo_copy(Iter first, Iter last, Iter out)
{
    // use loop calling copy constructors
}

template<class Iter>
Out copy(Iter first, Iter last, Iter out)
{
    // note: at least in this example, we could get rid of the tag completely and have a condition:
    //      IsPod_v<ValueType<Iter>>
    //
    // if constexpr gets a bit annoying with more than three cases, but it is extremely rare that you
    // would dispatch to more than two alternative implementations
    if constexpr (std::is_same_v<pod_tag, typename copy_trait<ValueType<Iter>>::tag>) {
        trivially_copy(first, last, out);
    }
    else {
        algo_copy(first, last, out);
    }
}

Even if you are using a tag, you can just as well check the tag with if constexpr and dispatch to functions with distinct names. In my opinion, uses of tag dispatch like above are antiquated and should no longer be recommended.

The second version:

I fail to see any legitimate reason why you would use tag dispatch in C++17 here. In C++20 you might also just create overloads with constraints like template <pod_type T>, which also seems easier and cleaner than tag dispatch.

Eisenwave commented 1 year ago

constexpr if (Steel Man)

template<class T> struct SimplyCopyable : std::false_type {};

template<> struct SimplyCopyable<int> : std::true_type {};

template<class Iter>
Out trivially_copy(Iter first, Iter last, Iter out)
{
    // use memmove
}

template<class Iter>
Out algo_copy(Iter first, Iter last, Iter out)
{
    // use loop calling copy constructors
}

template<class Iter>
Out copy(Iter first, Iter last, Iter out)
{
    if constexpr (SimplyCopyable<ValueType<Iter>>::value) {
        trivially_copy(first, last, out);
    }
    else {
        algo_copy(first, last, out);
    }
}

This is a more practical implementation using a constexpr if statement. Tag dispatch doesn't profit from boolean type traits much; we need tags. This is especially bad considering that type traits in the standard library are virtually all boolean. One of the only cases where we have tags is in std::iterator_traits.