CTAD

Hi all,

1
2
3
4
5
6
7
8
9
10
11
template <typename T>
struct Length {
    T sz;
};

void foo(Length len) { std::cout << len.sz; }

int main()
{
   foo(Length{5});
}
I get the error: class template placeholder 'Length' not permitted in this context | void foo(Length len) { std::cout << len.sz; }
1) I know that you say functions' parameters must have types and templates are not types and replacing Length in foo with auto (semi-template) solves the issue. But while Length as a (user-defined) type is just defined above foo, why doesn't the compiler yet look at it to know that Length is actually a type

Now, this one:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
template <typename T>
struct Length {
    T sz;
};

template <typename T, Length len>
struct array {
    T data[len.sz];
};

int main()
{
  array<int, {3u}> ar{1,2,3};
}
While this is more complicated than the fist code, why can the compiler easily recognize that Length in the template is a type!?
Last edited on
frek wrote:
... replacing Length in foo with auto (semi-template) solves the issue.

Not sure what you mean by "semi-template".

This
 
void foo(auto len) { std::cout << len.sz; }
is exactly equivalent to the following
 
template <typename T> void foo(T len) { std::cout << len.sz; }


frek wrote:
why doesn't the compiler yet look at it to know that Length is actually a type

Length is not a type. It is a template.

Length is a class template.
Length<int> is a class (i.e. a type).

Note that this is an error with the function declaration that happens before the compiler has even started to look at the code in main().


frek wrote:
...why can the compiler easily recognize that Length in the template is a type!?

The rules are a bit different for template parameters compared to normal function parameters. I think some people want it to be clear if something is a template. Right now you can look for the template keyword or auto parameter to decide whether something is a template or not. In your second example it's obvious that array is a template because you have used the template keyword but in your first example it wouldn't have been obvious that foo was a template by just looking at the declaration of foo.
Last edited on
Your answers are very obvious, thanks.
For the second example, my intention is more about using {3u} in main(). How do you explain it please?

EDIT: That error with the function declaration is a kind of syntax error I guess (since Length is not a built-in type). I also guess that the first step when the preprocessor's job is done is to look at the entire code for syntax errors (by the compiler) which starts from the beginning until reaching main() and so forth until end. If so, then the compiler sees the template declaration on lines 1 to 4 before reaching the function, so why can't it still recognize that Length is already defined?
Last edited on
1
2
3
4
5
6
7
8
9
10
11
12
13
14
template <typename T>
struct Length {
    T sz;
};

template <typename T, Length len>
struct array {
    T data[len.sz];
};

int main()
{
  array<int, {3u}> ar{1,2,3};
}


In your example, 3u is passed as an initializer to Length. C++20 allows the the type of Length to be deduced as int. You should note that this is not allowed in earlier versions of C++, this fails to compile with C++17.

I'd say. on the face of it, this is not good practice. Dunno if this code is an extract from a complex problem, but I think not.
Last edited on
frek wrote:
For the second example, my intention is more about using {3u} in main(). How do you explain it please?

Well, thanks to CTAD you can write:
 
Length len{3u};

What you're doing in your second example is very similar to that.

frek wrote:
... the compiler sees the template declaration on lines 1 to 4 before reaching the function, so why can't it still recognize that Length is already defined?

The problem is not that Length is not defined. The problem in your first code is that you're not allowed to write a function parameter like that. You need to write a type but Length is not a type. You're not allowed to write Length len; inside a function either (CTAD only works if you use an initializer).

They could of course have allowed this to work. They could have said that if you used a type template instead of a type then it worked similar to auto and automatically generated a function template.
 
void foo(Length len) { ... }
->
 
template <typename T> void foo(Length<T> len) { ... }
But this is not the case. That's why it's an error.
Last edited on
So the rule is like: Functions' parameters can be templates only if they're declared like a template, i.e., with angles <>, such as: Example<T> with template <typename T> declared on top of the function, OR, use auto instead. Right?

(CTAD only works if you use an initializer)
I didn't get this section!

I need to review CTAD. cppreference's page is (as usual) comprehensive but rather complex to follow. Do you have a similar alternative but with a simpler language/context?
Last edited on
Note that a class template is not a class, it's something you use to generate classes.
Similarly, a function template is not a function, it's something you use to generate functions.

Each function parameter always have one exact type.

If you declare a function parameter using a class template where you specify a type inside angle brackets then that's no longer a template. It's a type (a class type).

1
2
3
4
5
6
7
8
9
struct ExampleInt
{
	int x;
};

void foo(ExampleInt e) // ExampleInt is a class type
{
	std::cout << e.x << "\n";
}

1
2
3
4
5
6
7
8
9
10
template <typename T>
struct Example
{
	T x;
};

void foo(Example<int> e) // Example<int> is a class type
{
	std::cout << e.x << "\n";
}


foo is a function in both of the above examples. The only difference is that in the first example the type of the parameter is named ExampleInt and in the second example it's named Example<int>.

If you change foo to use template <typename T> or auto then foo is no longer a function. It's a function template.

1
2
3
4
5
template <typename T>
void foo(Example<T> e)
{
	std::cout << e.x << "\n";
}

T is a template argument and it must be known when you later call foo.

You can either specify it explicitly inside <>
 
foo<int>({5}); // T=int; calls foo<int> 
or you could let it deduce the type from the function arguments
 
foo(Example<int>{5}); // T=int; calls foo<int> 

Template argument deduction for function templates has existed in C++ from the beginning (AFAIK). It doesn't mean you can always rely on the template arguments to be deduced. The template argument might not be used in the function parameter list or it might be used multiple times and therefore be ambiguous if you supply different types. E.g. std::max(5, 7.0); will fail to compile because you are passing an int and a double and it's not clear which type to use.

Class template argument deduction (CTAD) is a new thing that was added in C++17 to allow similar deduction of template arguments for class templates. Just like with function templates it cannot work in all cases.

Examples when declaring a local variable:
 
Example e{5}; // T=int; e is an instance of type Example<int> 
 
Example e{}; // error: T cannot be deduced 

With user-defined constructors it can be even more tricky for the compiler to deduce the correct types. I'm no expert on this but I know you can write "deduction guides" to help CTAD work correctly in those cases.
Last edited on
Topic archived. No new replies allowed.