Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Which part of the C++ standard prevents explicitly specifying this template's arguments?

In my C++ travels I've come across the following idiom (for example here in Abseil) for ensuring that a templated function can't have template arguments explicitly specified, so they aren't part of the guaranteed API and are free to change without breaking anybody:

template <int&... ExplicitArgumentBarrier, typename T>
void AcceptSomeReference(const T&);

And it does seem to work:

foo.cc:5:3: error: no matching function for call to 'AcceptSomeReference'
  AcceptSomeReference<char>('a');
  ^~~~~~~~~~~~~~~~~~~~~~~~~
foo.cc:2:6: note: candidate template ignored: invalid explicitly-specified argument for template parameter 'ExplicitArgumentBarrier'

I understand at an intuitive level why this works, but I'm wondering how to make it precise. What section(s) of the standard guarantee that there is no way to explicitly specify template arguments for this template?

I'm surprised by clang's excellent error message here; it's like it recognizes the idiom.

like image 953
jacobsa Avatar asked Jun 28 '21 05:06

jacobsa


People also ask

What is a template argument C++?

In C++ this can be achieved using template parameters. A template parameter is a special kind of parameter that can be used to pass a type as argument: just like regular function parameters can be used to pass values to a function, template parameters allow to pass also types to a function.

Why do we use :: template template parameter?

Correct Option: D. It is used to adapt a policy into binary ones.

What is a non-type template parameter?

A template non-type parameter is a template parameter where the type of the parameter is predefined and is substituted for a constexpr value passed in as an argument. A non-type parameter can be any of the following types: An integral type. An enumeration type. A pointer or reference to a class object.

What is Typename C++?

" typename " is a keyword in the C++ programming language used when writing templates. It is used for specifying that a dependent name in a template definition or declaration is a type.

When do you not need to provide default arguments for parameters?

If you provide a default argument for the first parameter in a list, you do not need to provide default arguments for the remaining parameters. In a method header, the name is always followed by a set of parentheses. Which of the following is the best type of tool for breaking up input validation into separate steps?

Why do we need templates in C++?

When an object of a specific type is defined for actual use, the template definition for that class is substituted with the required data type. Suppose you write a function printData: To perform same operation with different data type, we have to write same code multiple time. C++ provides templates to reduce this type of duplication of code.

What is the Standard Template Library of C++?

C++ Programming Multiple Choice Questions & Answers (MCQs) on “Standard Template Library”. 1. What is the Standard Template Library? Clarification: STL expanded as Standard Template Library is set of C++ template classes to provide common programming data structures and functions.

What is a template parameter in C++?

Clarification: A template parameter is a special kind of parameter that can be used to pass a type as argument. 2. Which keyword can be used in template? 3. What is the validity of template parameters? Clarification: Template parameters are valid inside a block only i.e. they have block scope. 4. What will be the output of the following C++ code?


Video Answer


1 Answers

The main reason that a construct of the form

 template <int&... ExplicitArgumentBarrier, typename T>
 void AcceptSomeReference(T const&);

prohibits you from specifying the template argument for T explicitly is that for all template parameters that follow a template parameter pack either the compiler must be able to deduce the corresponding arguments from the function arguments (or they must have a default argument) [temp.param]/14:

A template parameter pack of a function template shall not be followed by another template parameter unless that template parameter can be deduced from the parameter-type-list ([dcl.fct]) of the function template or has a default argument ([temp.deduct]). A template parameter of a deduction guide template ([temp.deduct.guide]) that does not have a default argument shall be deducible from the parameter-type-list of the deduction guide template.

As pointed out by @DavisHerring this paragraph alone does not necessarily mean that it must be deducted. But: Finding the matching template arguments is performed in several steps: First the explicitly specified template argument list will be considered [temp.deduct.general]/2, only later on the template type deduction will be performed and finally default arguments are considered [temp.deduct.general]/5. [temp.arg.explicit]/7 states in this context

Note 3: Template parameters do not participate in template argument deduction if they are explicitly specified

This means whether a template argument can be deducted or not depends not only on the function template declaration but also on the steps before the template argument deduction is applied such as the consideration of the explicitly specified template argument list. A template arguments following a template parameter pack can't be specified explicitly as then it would not be participating in template argument deduction ([temp.arg.explicit]/7) and therefore would also not be deducted (which would violate [temp.param]/14).

When explicitly specifying the template arguments the compiler will therefore match them "greedily" to the first parameter pack (as for the following arguments either default values should be available or they should be deductible from function arguments! Counter-intuitively this is even the case if the template arguments are type and non-type parameters!). So there is no way of fully explicitly specifying the template argument list of a function with a signature

template <typename... ExplicitArgumentBarrier, typename T>
void AcceptSomeReference(const T&);

If it is called with

AcceptSomeReference<int,void,int>(8.0);

all the types would be attributed to the template parameter pack and never to T. So in the example above ExplicitArgumentBarrier = {int,void,int} and T will be deducted from the argument 8.0 to double.


To make things even worse you could use a reference as template parameter

template <int&... ExplicitArgumentBarrier, typename T>
void AcceptSomeReference(const T&);

It isn't impossible to explicitly specify template arguments for such a barrier but reference template parameters are very restrictive and it is very unlikely to happen by accident as they have to respect [temp.arg.nontype]/2 (constexpr for non-types) and [temp.arg.nontype]/3 (even more restrictive for references!): You would need some sort of static variable with the correct cv-qualifier (e.g. something like static constexpr int x or static int const x in the following example would not work either!) like in the following code snippet (Try it here!):

template <int&... ExplicitArgumentBarrier, typename T>
void AcceptSomeReference(const T& t) {
  std::cout << t << std::endl;
  return;
}

static int x = 93;
int main() {
  AcceptSomeReference<x>(29.1);
  return EXIT_SUCCESS;
}

This way by adding this variadic template of unlikely template arguments as the first template argument you can hold somebody off from specifying any template parameters at all. Whatever the users will try to put will very likely fail compiling. Without an explicit template argument list ExplicitArgumentBarrier will be auto-deducted to have a length of zero [temp.arg.explicit]/4/Note1.


Additional comment to @DavisHerring

Allowing somebody to explicitly specify the template arguments following a variadic template would lead to ambiguities and break existing rules. Consider the following function:

template <typename... Ts, typename U>
void func(U u);

What would the template arguments in the call func<double>(8.0) be? Ts = {}, U = double or Ts = {double}, U = double? The only way around this ambiguity would be to allow the user to only specify all the template arguments explicitly (and not only some). But then this leads again to problems with default arguments:

template <typename... Ts, typename U, typename V = int>
void func2(U u);

A call to func2<double,double>(8.0) is again ambiguous (Ts = {}, U = double, V = double or Ts = {double}, U = double, V = int). Now you would have to ban default template arguments in this context to remove this ambiguity!

like image 102
2b-t Avatar answered Oct 21 '22 00:10

2b-t