Reputation: 2489
I have a problem that can be minimized to the following example
#include <iostream>
#include <string>
class A{
public:
const char* chr;
A(){chr = "aaa";}
};
class B : A{
public:
const char* chr;
B(){chr = "bbb";}
};
template <class T>
std::string to_str(T) = delete;
template<>
inline std::string
to_str<A>(A object) {
std::string str;
return str.assign((object.chr));
}
int main() {
A a;
B b;
std::cout << to_str(b) << std::endl;
}
when changing it to std::cout << to_str(a) << std::endl;
the code runs and prints 'aaa
', but like this, it stops at compilation and outputs
main.cpp: In function 'int main()':
main.cpp:30:24: error: use of deleted function 'std::__cxx11::string to_str(T) [with T = B; std::__cxx11::string = std::__cxx11::basic_string<char>]'
std::cout << to_str(b) << std::endl;
^
main.cpp:18:13: note: declared here
std::string to_str(T) = delete;
^~~~~~
exit status 1
now lets say i have a lot of classes that inherit A
, can i 'tell' the compiler they all can go to the same function (the one accepts A
)?
Thanks.
Upvotes: 1
Views: 99
Reputation: 275405
Template function specialization does not work that way. There is no overload resolution; it merely permits replacing a specific function body with specific template arguments with your specialized one. It is rarely useful.
What you want is overload resolution, possibly with tag dispatch.
First remove this completely:
template <class T>
std::string to_str(T) = delete;
next, write an overload:
inline std::string to_str(A object) {
std::string str;
return str.assign((object.chr));
}
and done. Overload resolution dispatches B
to the A
overload.
Your next problem is slicing. B
has two members named chr
: A::chr
and B::chr
. For no good reason. In addition you are needlessly copying A
(or the A
subobject of B
).
inline std::string to_str(A const& object) {
std::string str;
return str.assign((object.chr));
}
this avoids a needless copy of A
.
class A{
public:
const char* chr;
A(){chr = "aaa";}
};
class B : public A{ // public, not private
public:
// const char* chr; // no need for this
B(){chr = "bbb";}
};
Upvotes: 1
Reputation: 41100
I question if this really is a minimized example of your problem. I think that what's happened is that some important details got lost in the translation, because the code you've shown us here has a number of issues.
B
inherits privately from A
. There is no way we can really treat a B
like an A
in this case. If you changed inheritance to public
, then we could attempt to force a B
in like so:
class B : public A{/*...*/};
// ...
std::cout << to_str(*static_cast<A*>(&b)) << std::endl;
But the output will remain "aaa", which leads me to my next points
Your to_str
specialization for A
accepts by value. This is important because even if we wanted to force a B
in, we end up slicing the object, this matters because
B
redefines the const char* chr
effectively hiding A::chr
, and since we've sliced, there's no way to recover B
's chr
.
We could start fixing things but first fixing the slicing by accepting A
by reference instead (or const
reference), and always preferring an overload instead of a template specialization for a function:
std::string to_str(A& object) {/*...*/}
B
's chr
from an instance of A
. We could go one of two ways here.
std::string
member in A
and do not redeclare it in any derived class, then derived classes can set it on initialization.Example:
class A{
public:
std::string chr;
A():chr{"aaa"}{}
};
class B : public A{
public:
B(){chr = "bbb";}
};
virtual const char* get_char()
method in A
that derived classes can override.Example:
class A{
public:
const char* chr;
A(){chr = "aaa";}
virtual const char* get_chr() const{return chr;}
};
class B : public A{
public:
const char* chr;
B(){chr = "bbb";}
const char* get_chr() const override {return chr;}
};
template <class T>
std::string to_str(T) = delete;
std::string to_str(A& object) {
std::string str;
return str.assign((object.get_chr()));
// ...
std::cout << to_str(*static_cast<A*>(&b)) << std::endl;
Note that at this point we're still forcing each B
to be an A
, which leads me to my next point
template <class T> std::string to_str(T) = delete;
will always exactly match every type you don't explicitly specialize for, being preferred in the worst case and causing ambiguity in the best case.
If you don't have any control over this function, then we're stuck with what we've got. However, if we do, then we can achieve what we need using type_traits
to accept anything that is derived from A
.
In this way we can keep your private inheritance, and also keep your redeclared chr
member, while simultaneously disabling to_str
for everything else and not requiring we static_cast
our b
.
Example:
#include <type_traits>
class A{
public:
const char* chr;
A(){chr = "aaa";}
};
class B : A{
public:
const char* chr;
B(){chr = "bbb";}
};
template<class T, class = std::enable_if_t<std::is_base_of<A, T>::value, int>>
inline std::string to_str(T& object) {
std::string str;
return str.assign((object.chr));
}
int main() {
A a;
B b;
std::cout << to_str(b) << std::endl;
}
Overall, I think that the best approach would be to give A
a protected std::string chr
that each derived class sets on initialization, and then have your to_string
function that is specialized for A&
(as an overload) print that member.
Edit: I forgot one last note. Issue #6: There are no virtual
members in A
. Therefore you will never be able to dynamic_cast
a pointer to A
to any derived class.
Upvotes: 1
Reputation: 66200
can i 'tell' the compiler they all can go to the same function(the one accepts A)?
Yes, using SFINAE and std::is_base_of
template <typename T>
typename std::enable_if<std::is_base_of<A, T>::value, std::string>::type
to_str (T const & t)
{ return t.chr; }
The following is a full working example
#include <type_traits>
#include <iostream>
#include <string>
struct A { char const * chr; A() : chr{"aaa"} {} };
struct B : A { char const * chr; B() : chr{"bbb"} {} };
struct C { char const * chr; C() : chr{"ccc"} {} };
template <typename T>
typename std::enable_if<std::is_base_of<A, T>::value, std::string>::type
to_str (T const & t)
{ return t.chr; }
int main()
{
A a;
B b;
C c;
std::cout << to_str(a) << std::endl; // print aaa
std::cout << to_str(b) << std::endl; // print bbb
// std::cout << to_str(c) << std::endl; // compilation error
}
Upvotes: 5
Reputation: 784
According to 14.7.3 [temp.expl.spec] paragraph 1, only non-deleted function templates may be explicitly specialized
C++ Standard Core Language Defect Reports and Accepted Issues, Revision 97
So, if you change
template <class T>
std::string to_str(T) = delete;
in, for example,
template <class T>
std::string to_str(T) { return ""; }
everything should work
Upvotes: -3