C++ Logo

std-proposals

Advanced search

Re: Proposal of adding 'explicit' in template declarations

From: Marcin Jaczewski <marcinjaczewski86_at_[hidden]>
Date: Sat, 26 Jun 2021 08:58:06 +0200
pt., 25 cze 2021 o 18:53 Михаил Найденов via Std-Proposals <
std-proposals_at_[hidden]> napisał(a):

> I must admit, not knowing which template argument to specify (and why!)
> can be annoying and frustrating.
>
> But there is another issue - specifying an argument that is not supposed
> to be specified!
> This is a much bigger problem, because it can change the behaviour of the
> program. It is essentially a cast.
>
> I was thinking, if we can get some magic trait/intrinsic/reflection which
> tells the function, if it is used with explicit arguments or not.
>
> template<class T>
> const auto& annoying_max(const T& a, const T& b)
> {
> static_assert(__is_deduced<T>, "You should not explicitly set the
> template param. Use cast on the arguments instead.")
> return a > b;
> }
>
>
"deduced" where? This template would give different answers depending where
it is used even if it refers to the same type from same source.
Probably more correct way to hande this is add `private class T` in
parameter list:
```
template<private class T>
void foo(T a);
//same behavior as
void foo(auto a);
```

with this calling
```
foo<int>(3);
```
will be a hard error.


> or as a concept
>
> template<class T>
> concept explict_targ = ! __is_deduced<T>;
>
> template<explict_targ T>
> T&& forward(T&& t)
>
>
> On Fri, Jun 25, 2021 at 8:16 AM Desmond Gold via Std-Proposals <
> std-proposals_at_[hidden]> wrote:
>
>> *Introduction *
>>
>> Usually, the template arguments are implicitly deduced by the compiler when not specifying the template arguments in such cases.
>>
>> There are times that you must specify the type template argument in order to resolve the ambiguity if and only if
>>
>> the return type is the template parameter (T).
>>
>>
>> Let us consider this little example (maybe ugly but only for the sake of example):
>>
>> template <typename T>
>> T initial_value() {
>> return T(0); }
>>
>> What you usually do is to specify the template argument otherwise you
>> would end up getting compilation error.
>>
>> initial_value<double>(); // ok
>> initial_value(); // error
>>
>> Another one:
>>
>> template <typename T> void force_print(T x) {
>> std::cout << x; }
>>
>> The template argument under the force_print function will be provided by
>> the compiler once the function argument is specified:
>>
>> force_print("hello"); // hello
>> force_print<float>(6.28); // 6.28 of type `double will be implicitly converted into `float`.
>>
>> There is no need to explicitly specify it when you don't need to because the compiler will do it for you.
>>
>>
>> In order to force the user to explicitly specify the template argument, we shall use std::type_identity_t:
>>
>> template <typename T> void force_print(std::type_identity_t<T> x) {
>> std::cout << x; }
>>
>> Or using explicit:
>>
>> template explicit <typename T> void force_print(T x) {
>> std::cout << x; }
>>
>> And then:
>>
>> force_print("hello"); // error
>> force_print<const char[]>("hello"); // ok
>>
>> *Sample Implementation on std::forward **(Taken from libstdc++)*
>>
>> template <typename T>
>> [[nodiscard]] constexpr T&& forward(std::remove_reference_t<T>& t) noexcept {
>> return static_cast<T&&>(t);
>> }
>>
>> template <typename T>
>> [[nodiscard]] constexpr T&& forward(std::remove_reference_t<T>&& t) noexcept {
>> static_assert(!std::is_lvalue_reference_v,
>> "template argument substituting T must not be an lvalue reference type");
>>
>> return static_cast<T&&>(t);
>> }
>>
>> Application:
>>
>> template <typename T>
>> constexpr T add_10(T&& x) {
>> return std::forward<T>(x) + 10;
>> }
>>
>> It seems normal, but when you didn't provide template argument like
>> std::forward(x) only, you will be given a compilation error.
>>
>> Error messages:
>>
>> error: no matching function for call to 'forward(int&)' note: candidate: 'template<class T> constexpr T&& std::forward(std::remove_reference_t<T>:&)' note: couldn't deduce template parameter 'T'
>>
>>
>> In my proposal, I shall use explicit for implementation:
>>
>> template explicit <typename T>
>> [[nodiscard]] constexpr T&& forward(std::remove_reference_t<T>& t) noexcept {
>> return static_cast<T&&>(t);
>> }
>>
>> template explicit <typename T>
>> [[nodiscard]] constexpr T&& forward(std::remove_reference_t<T>&& t) noexcept {
>> static_assert(!std::is_lvalue_reference_v,
>> "template argument substituting T must not be an lvalue reference type");
>>
>> return static_cast<T&&>(t);
>> }
>>
>> In this current implementation, the function forward would force the user
>> to specify the template argument.
>>
>> std::forward<T>(x); // ok
>> std::forward(x); // error
>>
>> Sample error message:
>>
>> error: in function 'std::forward', the template parameter T shall be explicitly specified. note: required from here: 'template <explicit class T> constexpr T&& std::forward(std::remove_reference_t<T>:&)'
>>
>>
>> *Motivation*
>>
>> There are some cases that we should specify the template arguments in order to resolve some ambiguity or the user's intent.
>>
>> We can use explicit so that we know what we are doing and expresses understandably that we should explicitly specify.
>>
>>
>> Common templates that would abide the rules of using explicit template argument are:
>>
>>
>> - *std::forward*
>> - *std::declval*
>> - *std::make_unique*
>> - *std::bitset*
>> - *std::bit_cast*
>> - *std::ranges::to (when shipped)*
>> - *... (etc)*
>>
>> For type aliases, concepts, and variable templates, the explicit keyword is optional since they are already needed to be specified explicitly.
>>
>> For function templates, and class templates, the explicit keyword is targeted when intended to use.
>>
>> When using explicit with class templates, it should disable the addition of CTAD and the deduction guides, otherwise the compiler will throw an error.
>>
>>
>> std::type_identity_t has already this kind of usage, but with explicit, it should really express our intent.
>>
>>
>> There are some limitations when using explicit with the template:
>>
>>
>> - In class template, it disables CTAD and the addition of a user-defined deduction guide.
>> - It prohibits the usage of default template arguments.
>> - The explicit can be used selectively inside template parameter list, but only in left-most orientation (somewhat similar to non-default parameter).
>>
>> *Specification **(Adapted from cppreference)*
>>
>> Every template <https://en.cppreference.com/w/cpp/language/templates> is parameterized by one or more template parameters, indicated in the parameter-list of the template declaration syntax:
>>
>> *template explicit*(optional) *<* parameter-list *>* declaration
>>
>>
>> Each parameter in parameter-list may be:
>>
>> - a non-type template parameter;
>> - a type template parameter;
>> - a template template parameter.
>>
>> A template declaration that is declared explicit shall apply to every template parameter that should be explicitly specified.
>>
>> For instance:
>>
>> *template explicit* *<typename T, typename U**>* *struct* A;
>>>
>> is equivalent to:
>>
>> *template* *<**explicit* *typename T,*
>>> *explicit* *typename U**>* *struct* A;
>>>
>> *Non-type template parameter*
>>
>>
>> 1. *explicit*(optional) type name (optional)
>> 2. *explicit*(optional) type *...* name(optional)
>> 3. *explicit*(optional) placeholder name
>>
>> Type template parameter
>>
>> 1. *explicit*(optional) type-parameter-key name(optional)
>> 2. *explicit*(optional) type-parameter-key *...* name(optional)
>> 3. *explicit*(optional) type-constraint name(optional)
>> 4. *explicit*(optional) type-constraint *...* name(optional)
>>
>> Template template parameter
>>
>> 1. *template* *explicit*(optional) *<* parameter-list *>* *explicit*(optional)* typename|class* name(optional)
>> 2. *template* *explicit*(optional) *<* parameter-list *>* *explicit*(optional)* typename|class...* name(optional)
>>
>> Lambda expressions
>> *[* captures *]*
>> *explicit*(optional) <tparams>(optional) *(* params *)* lambda-specifiers *{* body *}*
>> Explicit template parameters
>>
>> - For every template parameter that is declared as explicit, they shall be provided by the user during template invocation.
>>
>> template <typename T, typename U> struct A {
>> T data_1;
>> U data_2; };
>> template explicit <typename T, typename U> // same as template <explicit typename T, explicit typename U> struct B {
>> T data_1;
>> U data_2; };
>> template <explicit typename T, typename U = T> struct C {
>> T data_1;
>> U data_2; };
>>
>> A a1{15, 'A'}; // ok
>> A<double, bool> a2{3.14, true>; // ok
>> A<int, const std::ostream&> a3{4, std::cout}; // ok
>>
>> B b1{"hello", 6.0}; // error
>> B<int, std::string_view> b2{15, "earth"}; // ok
>> B<float> b3{13.0, 34.0}; // error
>>
>> C c1{nullptr, false}; // error
>> C<std::nullptr_t, bool> c2{nullptr, false}; // ok
>> C<int> c3{4, 5}; // ok
>>
>> template <typename... Ts>auto to_tuple(Ts&&... args) {
>> return std::forward_as_tuple(args...);}
>> auto tup1 = to_tuple(1, 'A', "hello"); // okauto tup2 = to_tuple<double, size_t, char>(1.429, 4, 'T'); // ok
>> template <explicit typename... Ts> // same as template explicit <typename... Ts>auto expl_to_tuple(Ts&&... args) {
>> return std::forward_as_tuple(args...);}
>> auto tup3 = expl_to_tuple(1, 'A', "hello"); // errorauto tup4 = expl_to_tuple<double, size_t, char>(1.429, 4, 'T'); // ok
>>
>> For lambda expressions, the accepted call style with template is ugly:
>>
>> auto compare = [] explicit <typename T>(T a, T b) {
>> return a < b; };
>>
>> compare(2, 50); // error
>> compare<int>(2, 50); // error
>> compare.template operator()<int>(2, 50); // ok
>>
>>
>> - Templates declared with explicit prohibits the usage of default
>> template arguments unless not every template parameter are declared as
>> explicit.
>>
>> template explicit <typename T, typename U> struct A0; // ok template explicit <typename T, typename U = int> struct A1; // error template <explicit typename T = int> struct A2; // error template <explicit typename T, typename U = T> struct A3; // ok template explicit <std::integral T> struct A4; // ok template <explicit typename T, typename... Ts> struct A5; // error
>>
>> template explicit <auto X> struct A6; // ok template <explicit std::floating_point auto F = 1.0> struct A7; // error template <explicit std::floating_point auto F> struct A8; // ok template <template <typename...> explicit typename> struct A9; // ok
>>
>>
>> - Doubling the explicit within the template declaration is not
>> allowed.
>>
>> template explicit <explicit T> struct A0; // error template explicit <template explicit <typename> typename T> struct A1; // ok template explicit <template explicit <typename> explicit typename T> struct A2; // error
>>
>>
>> - The order of the explicit template parameters must be on the
>> left-most side and skipping one is not allowed.
>>
>> template <explicit typename T, typename U> struct A0; // ok template <typename T, explicit typename U> struct A1; // error template <explicit typename T1, auto N2, explicit char N3> struct A2; // error
>>
>>
>> - Class template declaration with at least 1 explicit-declared
>> template parameter may not be allowed to add a deduction guides.
>> - Template Specialization - TBA...
>> - Variable templates, templated alias, and concepts declared with
>> explicit within template declaration may be ill-formed, no diagnostic
>> required.
>>
>> template explicit <typename T>
>> constexpr auto var1 = T(5); // ok
>> template explicit <int A, int B>
>> constexpr auto add = A + B; // ok
>> template <explicit int A, int B>
>> constexpr auto subtract = A - B; // ill-formed, no diagnostic required
>> template <explicit int A, explicit int B>
>> constexpr auto multiply = A * B; // ok
>> template <int A, explicit int B>
>> constexpr auto divide = A / B; // error
>> template explicit <size_t N> using int_array = std::array<int, N>; // ok
>> template <explicit typename R, typename... Args> using func = R(*)(Args...); // ill-formed, no diagnostic required
>> template explicit <typename T>
>> concept arithmetic = std::integral<T> || std::floating_point<T>; // ok
>> template <explicit typename T, size_t N>
>> concept less_N_bytes = sizeof(T) < N; // ill-formed, no diagnostic required
>>
>> Any suggestions? or opinions?
>>
>>
>>
>>
>>
>>
>>
>> --
>> Std-Proposals mailing list
>> Std-Proposals_at_[hidden]
>> https://lists.isocpp.org/mailman/listinfo.cgi/std-proposals
>>
> --
> Std-Proposals mailing list
> Std-Proposals_at_[hidden]
> https://lists.isocpp.org/mailman/listinfo.cgi/std-proposals
>

Received on 2021-06-26 01:58:18