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?
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