Is There Any Difference Between "T" and "Const T" in Template Parameter

Is there any difference between T and const T in template parameter?

No.

§14.1 [temp.param] p5

[...] The top-level cv-qualifiers on the template-parameter are ignored when determining its type.

What's the difference between const T & and T & in template deduction

It all comes down to the properties of expressions. 5 is an expression. It has a type and a value category. The type of 5 is int, not const int but just plain int. And it's a prvalue (that's the value category).

So when deducing T& against a 5, the type can only be deduced as int. Which means the synthesized function accepts a int&, which cannot bind to an rvalue.

When deducing T const&, the type T may still only be deduced as int under the appropriate provisions in template argument deduction. But now the synthesized function accepts a int const&, and that may bind to an rvalue just fine.

What is the difference between template class T and template typename T?

If by

template <typedef T> class SampleClass2

you mean

template <typename T> class SampleClass2

then there is no difference. The use of class and typename (in the context of a template parameter that refers to a type) is interchangeable.

The reason that both keywords are allowed here is historical. See this article for a detailed explanation.

How to enable conversion template argument T to const T?

Is there a better, "proper" way to do it?

There must be since your solution both has a weird behavior and is also invalid as specified by the C++ standard.

There's a rule called strict aliasing which dictate what kind of pointer type can alias another type. For example, both char* and std::byte* can alias any type, so this code is valid:

struct A {
// ... whatever
};

int main() {
A a{};
std::string b;

char* aptr = static_cast<void*>(&a); // roughtly equivalent to reinterpret
std::byte* bptr = reintepret_cast<std::byte*>(&b); // static cast to void works too
}

But, you cannot make any type alias another:

double a;
int* b = reinterpret_cast<int*>(&a); // NOT ALLOWED, undefined behavior

In the C++ type system, each instantiation of a template type are different, unrelated types. So in your example, Node<int> is a completely, unrelated, different type than Node<int const>.

I also said that your code has a very strange behavior?

Consider this code:

struct A {
int n;
A(int _n) : n(_n) { std::cout << "construct " << n << std::endl; }
A(A const&) { std::cout << "copy " << n << std::endl; }
~A() { std::cout << "destruct " << n << std::endl; }
};

Node<A> node1{A{1}};
Node<A> node2{A{2}};
Node<A> node3{A{3}};

node1.next = &node2;
node2.next = &node3;

Node<A const> node_const = node1;

This will output the following:

construct 1
construct 2
construct 3
copy 1
destruct 1
destruct 3
destruct 2
destruct 1

As you can see, you copy only one data, but not the rest of the nodes.


What can you do?

In the comments you mentionned that you wanted to implement a const iterator. That can be done without changing your data structures:

// inside list's scope
struct list_const_iterator {

auto operator*() -> T const& {
return node->value;
}

auto operator++() -> node_const_iterator& {
node = node->next;
return *this;
}

private:
Node const* node;
};

Since you contain a pointer to constant node, you cannot mutate the value inside of the node. The expression node->value yield a T const&.

Since the nodes are there only to implement List, I will assume they are abstracted away completely and never exposed to the users of the list.

If so, then you never have to convert a node, and operate on pointer to constant inside the implementation of the list and its iterators.

To reuse the same iterator, I would do something like this:

template<typename T>
struct iterator_base {
using reference = T&;
using node_pointer = Node<T>*;
};

template<typename T>
struct const_iterator_base {
using reference = T const&;
using node_pointer = Node<T> const*;
};

template<typename T, bool is_const>
using select_iterator_base = std::conditional_t<is_const, const_iterator_base<T>, iterator_base<T>>;

Then simply make your iterator type parameterized by the boolean:

template<bool is_const>
struct list_basic_iterator : select_iterator_base<is_const> {

auto operator*() -> typename select_iterator_base<is_const>::reference {
return node->value;
}

auto operator++() -> list_basic_iterator& {
node = node->next;
return *this;
}

private:
typename select_iterator_base<is_const>::node_ptr node;
};

using iterator = list_basic_iterator<false>;
using const_iterator = list_basic_iterator<true>;

C++ Template - passing const value of type T by reference

The issue is in incompatibility between pointers:

  • pointerToTestConst is of type const int* - non-const pointer to const integer. Therefore T=const int*
  • myList is of type list<int*>, deducing T=int*.

Since those types are not the same, compilation fails. Rightfully so, because elem would allow changing testConst if T=int*.

The issue manifests regardles you passing elem by (const) reference or value.

horribly unsafe, as the caller of this function need not pass an object of "const T" as second argument

So what? They get a compiler error about the comparison. It is no more unsafe than your code right now. I would argue that a generic ContainsElement should not care what you pass to is as long as it compares equal to some element in the list, it is a match.

Of course STL already offers this for std::find and std::ranges::find which also does not care and it is not called unsafe because of it.

const in template argument

It's ignored:

[C++11: 14.1/4]: A non-type template-parameter shall have one of the following (optionally cv-qualified) types:

  • integral or enumeration type,
  • pointer to object or pointer to function,
  • lvalue reference to object or lvalue reference to function,
  • pointer to member,
  • std::nullptr_t.

[C++11: 14.1/5]: [ Note: Other types are disallowed either explicitly below or implicitly by the rules governing the form of template-arguments (14.3). —end note ] The top-level cv-qualifiers on the template-parameter are ignored when determining its type.

The same wording is present at the same location in C++03.

This is partially because template arguments must be known at compile-time anyway. So, whether you have the const there or not, you may not pass some variable value:

template <int N>
void f()
{
N = 42;
}

template <int const N>
void g()
{
N = 42;
}

int main()
{
f<0>();
g<0>();

static const int h = 1;
f<h>();
g<h>();
}

prog.cpp: In function ‘void f() [with int N = 0]’:

prog.cpp:15: instantiated from here

prog.cpp:4: error: lvalue required as left operand of assignment

prog.cpp: In function ‘void g() [with int N = 0]’:

prog.cpp:16: instantiated from here

prog.cpp:10: error: lvalue required as left operand of assignment

prog.cpp: In function ‘void f() [with int N = 1]’:

prog.cpp:19: instantiated from here

prog.cpp:4: error: lvalue required as left operand of assignment

prog.cpp: In function ‘void g() [with int N = 1]’:

prog.cpp:20: instantiated from here

prog.cpp:10: error: lvalue required as left operand of assignment

Explanation of the C++ template function argument deduction when matching `T const &&t` against `int const *`

In the parameter declaration T const &&t, const is qualified on T, i.e. t is declared as an rvalue-reference to const T.

When ar with type const int * is passed, T is deduced as const int *, then the type of t would be const int * const &&, i.e. an rvalue-reference to const pointer to const int. Note that the consts are qualified on different things (on different levels), one for the pointer, one for the pointee.



Related Topics



Leave a reply



Submit