template specialization based on const-qualified instantiation - c++

Suppose I have a templated class:
template<typename T>
class A
{
public:
A(T& val) : m_val{val} {}
T& val() { return m_val; }
const T& val() const { return m_val; }
//etc...
private:
T& m_val;
};
Can I have template specialization which is different for lines (1) and (2):
int a = 5;
A<int> x {a}; //(1)
const A<int> y {a}; //(2)
const int b = 10;
const A<int> z {b}; //error: binding reference of type ‘int&’ to ‘const int’ discards qualifiers
Basically, in the const case I want to declare the underlying m_val as const T& m_val; because I've instantiated my wrapper class as const and I want to signify that the internal reference should also be const in this case.
Is this possible? I've tried several solutions and the only one that seem to work is to explicitly add const to the template argument like this: const A<const int> z {b};
Is it possible to avoid this inner const-qualifier?
EDIT: Changed the member variable to be a reference to show how const-ness can cause trouble for the compiler in this situation.
EDIT2: Removed the default constructor to simplify the problem.

Related

cannot const qualify type after type trait modification

Here's a minimal example:
#include <iostream>
#include <type_traits>
template <typename T>
struct Foo {
typedef typename std::decay<T>::type U;
const U s;
Foo(const T& val): s(val) {}
};
template <typename T>
Foo<T> make_foo(const T& val) {
return Foo<T>(val);
}
int main() {
make_foo("foo");
}
I expect Foo::s (or U) to be const-ed, but the compiler error I'm getting (on Ubuntu WSL g++ 7.4.0) is
bar.cpp: In instantiation of ‘Foo<T>::Foo(const T&) [with T = char [4]]’:
bar.cpp:14:12: required from ‘Foo<T> make_foo(const T&) [with T = char [4]]’
bar.cpp:18:19: required from here
bar.cpp:9:28: error: invalid conversion from ‘const char*’ to ‘Foo<char [4]>::U {aka char*}’ [-fpermissive]
Foo(const T& val): s(val) {}
^
which obviously means that neither U or Foo::s is a const char*. Thoughts?
EDIT:
For anyone running into this problem, the solution is exactly as StoryTeller points out in his post below. In other words, the const I had in make_foo(const T& val) in the parameter signature was stripping the const from the type parameter and making T evaluate to char [4]. And for some reason, I couldn't even grasp putting a const in the template angle brackets of each instance of Foo<...> (the return type and the expression returned):
#include <iostream>
#include <type_traits>
template <typename T>
struct Foo {
static_assert(std::is_same<T, const char[4]>::value);
typedef typename std::decay<T>::type U;
static_assert(std::is_same<U, const char*>::value);
U s;
Foo(const T& val): s(val) {}
};
template <typename T>
Foo<const T> make_foo(const T& val) {
static_assert(std::is_same<T, char[4]>::value);
return Foo<const T>(val);
}
int main() {
make_foo("foo");
}
Also, as chris pointed out as well, it's nice to spam static_assert and std::is_same to make dealing with templates in combination with const a bit more sane.
make_foo accepts by a const T &, and you pass in a constant array of 4 characters (that's what string literals are). Template argument deduction must therefore match a const char (&)[4] against the const T &. And since the const is specified in the parameter type, that leaves T as char[4]. The const qualifier is "consumed" by the function parameter.
So when you instantiated Foo<T>, you did so with char[4]. That type will decay to a char*, not a const char*. Adding const on top of it will only produce a char * const.
If you wish to preserve the const-ness of T, then amend your return statement (and type) to be
return Foo<const T>(val);

How to enforce constness over template type?

template<typename T> struct SomeClass{
void someFunc(const T& data) const {}
};
void testFunc(const int* a) {
SomeClass<int*> some_class;
some_class.someFunc( a);
}
I made a template instance with a non-const type. Now when calling a certain function I get errors that say:
error: invalid conversion from ‘const int*’ to ‘int*’
note: initializing argument 1 of ‘void SomeClass<T>::someFunc(const T&) const [with T = int*]’
So basically my const T& is treated as plain T&, the const is ignored. Why? How can I make sure in this case that it is seen by the compiler as const T&?
You may want to consider to partial specialize your class template SomeClass for the case T is a pointer. Then, add const to the type pointed to instead of the pointer itself (i.e., pointer to const instead of const pointer):
template<typename T> struct SomeClass<T*> {
void someFunc(const T* &data) const { /* ... */ }
};
SomeClass<int*>::someFunc() (i.e., T = int*) will be instantiated to:
void someFunc(const int* &data) const;
data above is a reference to a pointer to const int. However, with your primary template, SomeClass<int*>::someFunc() is actually:
void someFunc(int* const &data) const;
That is, data here is a reference to a const pointer to int. Therefore, you can't pass a, which is a const int*, as an argument to someFunc() since that pointed const int would be modifiable through the parameter data. In other words, the constness would be lost.
You need to change your definition to SomeClass<const int*> some_class;. The T comes from the definition and is int*, compiler is complaining rightfully.
The const is not ignored. It's applied to the type int*, yielding an int* that cannot be modified, i.e., int* const. In const int*, the const applies to the int, not to the pointer. That is, const int* points at an int that cannot be modified.
Inside testFunc you end up both consts. Since it's called with a const int*, the specialization of SomeClass has to be SomeClass<const int*>. And then when you call someFunc you get the second one; the actual argument type is const int* const. The first const applies to the int and the second const applies to the argument itself, i.e., to the pointer.
Assuming that the code base is huge and therefore you can't afford to write a specialization for your class template, you could provide the following delegating member template, someFunc(), which is an overload of your original member function:
#include <type_traits>
template<typename T> struct SomeClass {
// your original member function
void someFunc(const T &data) const { /* ... a lot of stuff ... */ }
// delegating member template
template<typename S>
void someFunc(const S* &data) const {
// delegate to original function
someFunc(const_cast<S*>(data));
}
};
First, this member template only comes into play with pointer arguments. Second, what it really does is to delegate the call to your original member function with the same name by casting out the const from the pointed type.
I hope it helps.

passing const this to function accepting const pointer is not const-correct?

I have a class template Foo with the following member function:
bool contains(const T& item) const
I have instantiated this with a pointer type: Foo<Bar*>, leading me to expect that the member function will now have the following signature:
bool contains(const Bar*& item) const
In a const Bar member function, I attempt to pass this to Foo<Bar*>::contains:
bool Bar::func(const Foo<Bar*>& foo) const
{
return foo.contains(this);
}
This fails to compile, with the following error:
error: invalid conversion from ‘const Bar*’ to ‘Bar*’
Question:
Why is my const T& parameter not const-correct?
What signature for Foo<T>::contains(...) const is required to allow calling with this to compile?
Full example:
#include <vector>
#include <algorithm>
template<typename T>
struct Foo
{
bool contains(const T& item) const
{
return false;
}
};
struct Bar
{
bool func(const Foo<Bar*>& foo) const
{
return foo.contains(this);
}
};
Error output:
scratch/main.cpp:17:33: error: invalid conversion from ‘const Bar*’ to ‘Bar*’ [-fpermissive]
return foo.contains(this);
^
scratch/main.cpp:7:10: note: initializing argument 1 of ‘bool Foo<T>::contains(const T&) const [with T = Bar*]’
bool contains(const T& item) const
I have instantiated this with a pointer type: Foo<Bar*>, leading me
to expect that the member function will now have the following
signature:
bool contains(const Bar*& item) const
That's where the problem is. When T = Bar*, the expression
bool contains(const T& item) const
Will actually compile to
bool contains(Bar * const & item) const
That is, a reference-to-a-const-pointer-to-Bar.
It makes sense of you think about it: you want T to be const, and then you want a reference to that.
If you want to apply the const in the usual "intended" way (though this might cause some surprises for seasoned C++ programmers), you can declare your container and member function in the following way:
template <class T>
class Container {
public:
using const_bare_type = typename std::conditional<
std::is_pointer<T>::value,
typename std::remove_pointer<T>::type const*,
const T>::type;
bool contains(const const_bare_type& item);
};
Compiler alerts about wrong line, you must write:
bool func(const Foo<const Bar*>& foo) const
I.e. const Bar* in template parameter, because Bar::func receives const Bar * this as its parameter, and cannot convert it to Bar* in template parameter (cannot remove const).

C++ is possible to make only one method templated?

I have a class and I need only one template method, like this:
/* A.h */
class A {
public:
void foo() const;
private:
template <class T>
void foo2(const T& t, const std::string& s) {
/* */
}
}
This compiles fine, but if in the foo specialization I try to call foo2 I get errors:
/* A.cpp */
void A::foo() {
this->foo2(1, "test");
}
The error is:
passing ‘const A’ as ‘this’ argument of ‘void A::foo2(const T&, const
string&) [with T = int, std::string = std::basic_string<char>]’
discards qualifiers [-fpermissive]
Yes, it is possible.
I think you missed const modifier for void foo() in your example, didn't you?
If my assumption is correct, put a const modifier for foo2 too.
You are calling on the same object a non-const function foo2 from const function foo. Hence the error. BTW this in the call is not necessary:
this->foo2(1, "test");
the following will do too:
foo2(1, "test");
You're missing a ; at the end of your class declaration and your const correctness seems to be inconsistent. This compiles for me in vc12:
/* A.h */
class A {
public:
void foo() const;
private:
template <class T>
void foo2(const T& t, const std::string& s) const {
/* */
}
};
void A::foo() const {
this->foo2(1, "test");
}

Template accessor can not convert from T to T& (C++)

I have a templated class:
template<class T>
class MyClass
{
public:
MyClass(T mem)
{
member = mem;
}
T& GetMember() const
{
return(member);
}
T member;
};
and then I do this:
MyClass<bool> test(true);
bool someBool = test.GetMember();
And I get a compile error saying it can't convert 'bool' to 'bool&'
How can I fix this?
The problem is you have a const member function, but you are returning a mutable reference from it. (btw cl's error message is error C2440: 'return' : cannot convert from 'const bool' to 'bool &' which makes that clear). That is probably is not your real intent, so either use
T GetMember() const
{
return member;
}
or
const T& GetMember() const
{
return member;
}
T& GetMember() const
should be
const T& GetMember() const
const is not just a keyword to make your code look safer, it acutally enforces your code to be safer ;)