Template constructor versus no template constructor in class any

Imagine something like boost::any

:

class any {
public:
  any();
  any(const any &);
  any(any &&);
  template<typename ValueType> any(const ValueType &);
  template<typename ValueType> any(ValueType &&);

      

Will the appropriate (copy / move) constructor be called for any possible any

? Or it should be written with SFINAE for example. eg:

template<typename ValueType,
  typename = typename std::enable_if<
    !std::is_same<any, typename std::decay<ValueType>::type>::value
  >::type>
  any(const ValueType& value)
template<typename ValueType,
  typename = typename std::enable_if<
    !std::is_same<any, typename std::decay<ValueType>::type>::value
  >::type>
  any(ValueType&& value)

      

Question: Do I need to protect the templated constructor (build any

from some value), or can I leave it, because a constructor without a template (copy / move) will always work for any

? How about a modifier volatile

or something weird std::move((const any&)it)

if possible?

An answer describing finding a constructor would be most helpful, thanks.

EDIT: Building any

containing both any

, would be a problem, I definitely want to avoid this (SFINAE ensures that this will not happen).

+3


source to share


2 answers


With C ++ 11 and the implementation of the Universal Reference (and a constructor with that parameter), the overload resolution rules will pick the templated version.

The truth is that if the compiler can choose between a template and a non-templated function, it will come with a non-template. But it will only do this if they are equally good :

ยง 13.3.3 Best viable function [over.match.best]

  • [...] Given these definitions, a viable function F1 is defined as a better function than another viable function F2 if, for all arguments i, ICS i(F1) is no worse than ICS i (F2) and then

    - for some argument j, ICS j(F1) is a better conversion than ICS j(F2), or if not that ,

    [...]

    - F1 is a function without a template, and F2 is a specialized function of a template, [...]

However, having two constructors declared as below:

any(const any &);

template <typename ValueType>
any(const ValueType &);

      

the compiler will choose the non-templated version, since instantiating the template would result in exactly the same declaration .

However, when the constructor accepts an Unviersal Reference, the situation changes radically:

any(const any &);

template <typename ValueType>
any(ValueType &&);

      

In the context of copying an instance with regular direct initialization syntax:

any a;
any b{a};

      

the evaluated type a

is an lvalue any &

with no modifier const

. After generating a set of candidate constructors for overload resolution, the compiler ends with the following signatures:

any(const any &); // non-template

any(any &); // instantiated template

      



And then:

ยง 13.3.1 Candidate functions and argument lists [over.match.funcs]

  1. In each case where the candidate is a function template, the specialized candidate function templates are generated using template argument subtraction (14.8.3, 14.8.2). These candidates are then processed as candidate functions in the usual way. This name can refer to one or more function templates, as well as a set of overloaded functions without templates. In this case, the candidate functions generated from each function template are combined with a set of non-standard candidate functions.

That is, the version of the template matches better , and that's what the compiler chooses.

However, if you had:

const any a; // const!
any b{a};

      

Then this time, the constructor signature generated from the constructor using the Universal Reference will be the same as without the copy-constructor version template, so only then the non-templated version is called.

How about a mutable modifier or some weird std :: move ((const any &) it) if possible?

The same happens. The Universal Reference constructor has the best match.

That is, std::move((const any&)it)

evaluates a type expression const any &&

.

The parameter of a non-template move constructor can take a non-const rvalue parameter (so it doesn't match at all, since it doesn't need a modifier const

).

A parameter to a non-templated constructor can accept a const lvalue reference (this is fine, an rvalue constant can be bound by an lvalue constant reference, but is not an exact match ).

Then the instantiated template using the Universal Reference will again be the best match to be called.

+6


source


Typically, if a template and a non-template function are equally good, the non-template version is chosen over the template version. Since your copy / move constructors any

are not templates, they take precedence over template constructors for rvalues โ€‹โ€‹or lvalues โ€‹โ€‹constants.

However, due to the special rules for rvalue reference templates, the inferred type for template<typename ValueType> any(ValueType &&);

will be the any&

one that matches better. So when you copy a non-const lvalue, you call the templated constructor.



So you need an SFINAE rule for this templated constructor, but not for a templated constructor that accepts an lvalue reference for const.

+3


source







All Articles