constrained member functions versus explicit class instantiations

74 views
Skip to first unread message

Andrzej Krzemieński

unread,
Sep 14, 2016, 6:41:37 AM9/14/16
to SG8 - Concepts
Hi Everyone,
 I would like to know what the specs say about the following situation, and what they should be saying.

Suppose I have the following wrapper class:

template <typename T>
struct Wrapper
{
    T val
;

   
explicit Wrapper(T && v)
     
: val(std::move(v)) {}

   
explicit Wrapper(T const & v)
       
: val(v) {}
};

I can use it with std::unique_ptr:

Wrapper<std::unique_ptr<int>> w { std::make_unique<int>(1) };

Note that the second constructor is ill-formed if instantiated for std::unique_ptr, but since it is not instantiated, everything is fine. Now, as it is often the case with templates, you do not know if they have bugs until you instantiate them, so I am doing an explicit class template instantiation in order to test member functions' implementation:

template struct Wrapper<std::unique_ptr<int>>;

I get a compile-time error because the attempt to instantiate the second constructor fails. So, I constrain the second constructor:

template <typename T>
struct Wrapper
{
    T val
;

   
explicit Wrapper(T && v)
     
: val(std::move(v)) {}

   
explicit Wrapper(T const & v)
    requires std
::is_copy_constructible<T>::value
       
: val(v) {}
};

My question is, with this constrain, should the explicit specialization of the class template succeed? Or, in other words, if a member function is 'eliminated' by the constraint, should it be instantiated during explicit class template instantiation? I know it is instantiated in GCC 6, but this feels wrong, doesn't it?

What do the specs say? And if they agree with GCC, is there a reason for this behavior?

Regards,
&rzej;

Ville Voutilainen

unread,
Sep 14, 2016, 7:02:24 AM9/14/16
to conc...@isocpp.org
Well, as far as I understand, this issue arises with or without
concepts - an explicit instantiation
of a class with mutually-exclusively-constrained constructor templates
will try to instantiate them all, and
the explicit instantiation will fail miserably. This is currently an
issue with pair, tuple and optional.

Luckily, there's a good chance we have a way to solve that problem
without changing how explicit
instantiations work, because we have a language facility that can
completely disable the code
in the definition. That language facility is "if constexpr". It was a
fairly recent finding that it
can be used for such things, and that stemmed out from using the
so-called "Eric's trick(*)" to
pseudo-sfinae special member functions of class templates. If the
"false case" ever gets instantiated
for such a trick, the definition is a hard error. Using if constexpr
can, in most cases(**) alleviate
that problem, so the "true case" has the normal code and the "false
case" is just empty, a no-op.


(* use a parameter that is not const T&/T&&, but rather
conditional_t<constraint_v<T>, const T&, const nonesuch&>)
(**) in cases where the return type is a reference and *this can't be
used, it can be tricky to return something.
This is, on the other hand, not a probler for e.g. copy/move
assignment operators, but can be a problem for other
member functions.

Andrzej Krzemieński

unread,
Sep 14, 2016, 8:05:28 AM9/14/16
to SG8 - Concepts

I understand that you are offering a temporary workaround, but still leave the question open: what should Concepts Lite do. I understand that you can do such a thing with arbitrary amount of meta-programming magic, but I would rather see a solution that is easily understood by an average programmer.

Ville Voutilainen

unread,
Sep 14, 2016, 8:21:21 AM9/14/16
to conc...@isocpp.org
Indeed. A constraint on a function ensures that the function will
never be called
out-of-constraints. So the question indeed becomes, what should
explicit instantiation
do, and what we believe the programmer intended.

One option is to examine the constraints of a function during explicit
instantiation
and simply not define the functions for which the constraints aren't
satisfied. That
is more work for the compiler, but would be perhaps palatable considering that
explicit instantiations are arguably rarely used.

If we think explicit instantiation means "instantiate all members and
tell me if you can't",
then the status quo has merit. I'm not so sure that's useful, because
as things are
currently, you can't, you just can't, provide an explicit
instantiation for tuple or pair or optional,
with or without concepts.
That seems unfortunate. We can hack it to work with "if constexpr",
but I think your
question about whether that's how things should work is certainly a good one.

Roland Bock

unread,
Sep 14, 2016, 8:52:59 AM9/14/16
to conc...@isocpp.org
On 2016-09-14 12:41, Andrzej Krzemieński wrote:
> Hi Everyone,
> I would like to know what the specs say about the following situation,
> and what they should be saying.
>
> Suppose I have the following wrapper class:
>
> |
> template<typenameT>
> structWrapper
> {
> T val;
>
> explicitWrapper(T &&v)
> :val(std::move(v)){}
>
> explicitWrapper(T const&v)
> :val(v){}
> };
> |
>
> I can use it with std::unique_ptr:
>
> |
> Wrapper<std::unique_ptr<int>>w {std::make_unique<int>(1)};
> |
>
> Note that the second constructor is ill-formed if instantiated for
> std::unique_ptr, but since it is not instantiated, everything is fine.
> Now, as it is often the case with templates, you do not know if they
> have bugs until you instantiate them, so I am doing an explicit class
> template instantiation in order to test member functions' implementation:
>
> |
> templatestructWrapper<std::unique_ptr<int>>;
> |
>
> I get a compile-time error because the attempt to instantiate the second
> constructor fails. So, I constrain the second constructor:
>
> |
> template<typenameT>
> structWrapper
> {
> T val;
>
> explicitWrapper(T &&v)
> :val(std::move(v)){}
>
> explicitWrapper(T const&v)
> requires std::is_copy_constructible<T>::value
> :val(v){}
> };
> |
>
> My question is, with this constrain, should the explicit specialization
> of the class template succeed? Or, in other words, if a member function
> is 'eliminated' by the constraint, should it be instantiated during
> explicit class template instantiation? I know it is instantiated in GCC
> 6, but this feels wrong, doesn't it?
>
> What do the specs say? And if they agree with GCC, is there a reason for
> this behavior?

My understanding is that functions which do not fulfil the requirements
are removed from the overload set.

Thus, the explicit specialization should work and the copy constructor
is gone. Seems straight forward to me.

Cheers,

Roland

Andrzej Krzemieński

unread,
Sep 14, 2016, 9:06:46 AM9/14/16
to SG8 - Concepts

I agree with the final conclusion; but note that there are no overload sets in question when we are considering explicit instantiation of a class template.

Roland Bock

unread,
Sep 14, 2016, 9:45:51 AM9/14/16
to conc...@isocpp.org
Yes, overload set is the wrong term :-)
But as I understand it, a function/constructor basically gone(*), if the
constraint is not fulfilled.



(*) non-standard-lingo

Andrew Sutton

unread,
Sep 14, 2016, 12:01:07 PM9/14/16
to conc...@isocpp.org
template struct Wrapper<std::unique_ptr<int>>;

I get a compile-time error because the attempt to instantiate the second constructor fails. So, I constrain the second constructor:

template <typename T>
struct Wrapper
{
    T val
;

   
explicit Wrapper(T && v)
     
: val(std::move(v)) {}

   
explicit Wrapper(T const & v)
    requires std
::is_copy_constructible<T>::value
       
: val(v) {}
};

My question is, with this constrain, should the explicit specialization of the class template succeed? Or, in other words, if a member function is 'eliminated' by the constraint, should it be instantiated during explicit class template instantiation? I know it is instantiated in GCC 6, but this feels wrong, doesn't it?

In general, members are not "eliminated' by their constraints. They simply become uncallable -- especially in the implicit instantiation case.

Explicit instantiation is worded a little differently. Effectively, you will not get an explicit instantiation of the copy constructor's declaration or definition since the constraints aren't satisfied. So, this should essentially eliminate the member.

Although this a bit asymmetric and could probably be changed to be consistent with implicit instantiation (i.e., you get a declaration that is always uncallable, but not definition).


--
Andrew Sutton

Casey Carter

unread,
Sep 14, 2016, 12:43:51 PM9/14/16
to SG8 - Concepts
On Wednesday, September 14, 2016 at 9:01:07 AM UTC-7, Andrew Sutton wrote:
template struct Wrapper<std::unique_ptr<int>>;

I get a compile-time error because the attempt to instantiate the second constructor fails. So, I constrain the second constructor:

template <typename T>
struct Wrapper
{
    T val
;

   
explicit Wrapper(T && v)
     
: val(std::move(v)) {}

   
explicit Wrapper(T const & v)
    requires std
::is_copy_constructible<T>::value
       
: val(v) {}
};

My question is, with this constrain, should the explicit specialization of the class template succeed? Or, in other words, if a member function is 'eliminated' by the constraint, should it be instantiated during explicit class template instantiation? I know it is instantiated in GCC 6, but this feels wrong, doesn't it?

In general, members are not "eliminated' by their constraints. They simply become uncallable -- especially in the implicit instantiation case.

Explicit instantiation is worded a little differently. Effectively, you will not get an explicit instantiation of the copy constructor's declaration or definition since the constraints aren't satisfied. So, this should essentially eliminate the member.


To clarify Andrew's response: the behavior observed in the OP is a bug in GCC. P0121R0 modifies [temp.explicit]/8 as follows:

An explicit instantiation that names a class template specialization is also an explicit instantiation of the same kind (declaration or definition) of each of its members (not including members inherited from base classes and members that are templates) that has not been previously explicitly specialized in the translation unit containing the explicit instantiation, and provided that the associated constraints, if any, of that member are satisfied by the template arguments of the explicit instantiation (14.10.2), except as described below.

The members whose constraints are not satisfied should not be explicitly instantiated. 

Ville Voutilainen

unread,
Sep 14, 2016, 12:46:35 PM9/14/16
to conc...@isocpp.org
On 14 September 2016 at 19:43, Casey Carter <cart...@gmail.com> wrote:
> To clarify Andrew's response: the behavior observed in the OP is a bug in
> GCC. P0121R0 modifies [temp.explicit]/8 as follows:
>
> An explicit instantiation that names a class template specialization is also
> an explicit instantiation of the same kind (declaration or definition) of
> each of its members (not including members inherited from base classes and
> members that are templates) that has not been previously explicitly
> specialized in the translation unit containing the explicit instantiation,
> and provided that the associated constraints, if any, of that member are
> satisfied by the template arguments of the explicit instantiation (14.10.2),
> except as described below.
>
>
> The members whose constraints are not satisfied should not be explicitly
> instantiated.


I wonder whether we could make that so for the current
pseudo-constraints (enable_if)
as well.

Casey Carter

unread,
Sep 14, 2016, 1:32:17 PM9/14/16
to SG8 - Concepts
Standard C++ constrained members are templates ("not including...members that are templates") and therefore not explicitly instantiated.

Andrew Sutton

unread,
Sep 14, 2016, 1:35:18 PM9/14/16
to conc...@isocpp.org
To clarify Andrew's response: the behavior observed in the OP is a bug in GCC. P0121R0 modifies [temp.explicit]/8 as follows:

An explicit instantiation that names a class template specialization is also an explicit instantiation of the same kind (declaration or definition) of each of its members (not including members inherited from base classes and members that are templates) that has not been previously explicitly specialized in the translation unit containing the explicit instantiation, and provided that the associated constraints, if any, of that member are satisfied by the template arguments of the explicit instantiation (14.10.2), except as described below.

The members whose constraints are not satisfied should not be explicitly instantiated. 


Thanks. I couldn't remember if GCC was doing the right thing or not.

--
Andrew Sutton

Ville Voutilainen

unread,
Sep 14, 2016, 1:44:26 PM9/14/16
to conc...@isocpp.org
On 14 September 2016 at 20:32, Casey Carter <cart...@gmail.com> wrote:
>> > The members whose constraints are not satisfied should not be explicitly
>> > instantiated.
>>
>>
>> I wonder whether we could make that so for the current
>> pseudo-constraints (enable_if)
>> as well.
>
>
> Standard C++ constrained members are templates ("not including...members
> that are templates") and therefore not explicitly instantiated.


Clarification: I was talking about whether we could do that for C++17,
so P0121 doesn't help me.

Richard Smith

unread,
Sep 14, 2016, 1:48:26 PM9/14/16
to conc...@isocpp.org
Right; as Casey says, this already works for std::enable_if in C++17, because std::enable_if only works for member function templates (not for non-template member functions of class templates), and an explicit instantiation of a class template specialization has no effect on its member function templates.

Anton Bikineev

unread,
Sep 14, 2016, 1:51:28 PM9/14/16
to SG8 - Concepts
It works for C++ < 17 too, consider this:

template <class T>
struct A
{
   
template <class T1 = T, std::enable_if_t<std::is_integral_v<T1>>* = nullptr>
   
void foo()
   
{
       T t
;
       t
.foo();
   
}
 
};


template struct A<int>; // well-formed, no error


среда, 14 сентября 2016 г., 20:44:26 UTC+3 пользователь Ville Voutilainen написал:

Casey Carter

unread,
Sep 14, 2016, 2:15:47 PM9/14/16
to SG8 - Concepts
On Wednesday, September 14, 2016 at 10:51:28 AM UTC-7, Anton Bikineev wrote:
It works for C++ < 17 too, consider this:



FWIW, the text in question was added to C++14 by the resolution of core defect 1532 (http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#1532).

Anton Bikineev

unread,
Sep 14, 2016, 2:17:23 PM9/14/16
to SG8 - Concepts
good note, thanks

среда, 14 сентября 2016 г., 21:15:47 UTC+3 пользователь Casey Carter написал:

Andrzej Krzemieński

unread,
Sep 14, 2016, 3:32:41 PM9/14/16
to SG8 - Concepts

Casey, Andrew,
Thank you for the clarification. This is good news.

Regards,
&rzej;
 
Reply all
Reply to author
Forward
0 new messages