C++ Logo

std-proposals

Advanced search

[std-proposals] How should we tag a class? Use an empty base class or a typedef?

From: Frederick Virchanza Gotham <cauldwell.thomas_at_[hidden]>
Date: Sat, 25 May 2024 18:04:09 +0100
In my paper on "std::elide" P3288R0, I wanted the instantiation of a
template to fail if the parameter type was a specialization of
"std::elide", so I applied a constraint as follows:

    template<typename T>
    requires (!is_specialization_v< std::remove_cvref_t<T>, std::elide>)
    AwkwardClass(T &&arg) noexcept
    {
        std::cout << "In constructor for AwkwardClass, type of T = "
<< typeid(T).name() << std::endl;
    }

I wrote in the paper that the proposed wording for the Standard should
be as follows under the heading of when template instantiation should
fail:

    "Attempting to instantiate a constructor in which a parameter has
a type which is a specialization of std::elide"

Later I found that this was too restrictive, because I found a
use-case in which you'd want the instantiation to succeed if the
function has more than one parameter. And so I considered changing the
wording to:

    "Attempting to instantiate a constructor which has only one
parameter, and the sole parameter type is a specialization of
std::elide"

This would mean changing the constraint as follows:

    template<typename... Ts>
    requires (!( (1u==sizeof...(Ts)) && (is_specialization_v<
std::remove_cvref_t<Ts>, std::elide>|| ...) ))
    AwkwardClass(Ts&&... arg) noexcept
    {
        ( (std::cout << "In constructor for AwkwardClass, type of T =
" << typeid(Ts).name() << std::endl), ... );
    }

But then I thought of how Duff's Device became possible. Duff's Device
only became possible because very little restriction was placed on
switch statements and do-while loops. So if I want "std::elide" to be
as versatile as possible, I want to put some sort of tag on it -- a
tag which can be used by other programmers when writing their own
classes.

------ Possibility No. 1: Derive std::elide from std::elide_base

So it would be like this:

    class elide_base {};

    template<typename F, typename... Params>
    class elide final : public elide_base { . . . };

This would mean that the programmer has the freedom to write their own
class similar to "std::elide", and the template instantiation will
fail (as intended) so long as the programmer derives their own elider
class from "std::elide_base", and so then the proposed word for the
standard would be:

    "Attempting to instantiate a constructor which has only one
parameter, and the sole parameter type is a class type derived from
std::elide_base"

And so then the template constraint would become:

    template<typename... Ts>
    requires (!( (1u==sizeof...(Ts)) && (std::derived_from<
std::remove_cvref_t<Ts>, std::elide_base > || ...) ))
    AwkwardClass(Ts&&... arg) noexcept
    {
        ( (std::cout << "In constructor for AwkwardClass, type of T =
" << typeid(Ts).name() << std::endl), ... );
    }

------ Possibility No. 2: Use a typedef tag

So it would be like this:

    template<typename F, typename... Params>
    class elide final {
    public:
        typedef int tag_elider;
    private:
        . . .
    };

And then the constraint would be like this:

    template<typename... Ts>
    requires ( !( (1u==sizeof...(Ts)) && (requires { typename
Ts::tag_elider{}; } || ...) ) )
    AwkwardClass(Ts&&... arg) noexcept
    {
        ( (std::cout << "In constructor for AwkwardClass, type of T =
" << typeid(Ts).name() << std::endl), ... );
    }

And so then the proposed wording for the Standard would be:

    "Attempting to instantiate a constructor which has only one
parameter, and the sole parameter type is a class type which contains
a public typedef called 'tag_elider'"

---------------- End of Possibilities

So what should be the canonical way of doing this in C++26, not just
for the standard library but also for programmers writing their own
classes?

Received on 2024-05-25 17:04:18