Reputation: 21160
Say we have a class that can write stuff to output
class Writer
{
public:
int write(const std::string& str);
int write(const char* str, int len);
//...
};
I was fine with this, its flexible and all that, until I realized
char* buf = new char[n]; //not terminated with '\0'
//load up buf
Writer w;
w.write(buf); //compiles!
That is a really nasty bug.
We can amend somewhat with some templating
class WriterV2
{
public:
int write(const std::string& str);
int write(const char* str, int len);
template<typename... Args>
int write(const char*, Args...)
{ static_assert(sizeof...(Args) < 0, "Incorrect arguments"); }
//...
};
But this method has its problems
WriterV2 w;
w.write("The templating genius!"); //compile error
What do I do? What is a better design?
And before anyone asks, overloading for const char (&)[N]
does not work. It might be feasible to create a wrapper to do this, but that seems... overkill?
EDIT Adding a method write(char*)
and emitting an error there is not ideal. When passing buf
around through functions and all that, it might become const char*
.
Upvotes: 7
Views: 122
Reputation: 21576
ICS (Implicit Conversion Sequences) during overload resolution in C++ can produce surprising results as you've noticed, and also quite annoying..
You can provide necessary interfaces you need, then carefully employ templates to handle the string literal vs const char*
fiasco by taking advantage of partial ordering to delete
the unwanted overload.
Code:
#include <iostream>
#include <string>
#include <type_traits>
class Writer
{
public:
int write(std::string&&) { std::cout << "int write(std::string)\n"; return 0; }
int write(const std::string&) { std::cout << "int write(const std::string& str)\n"; return 0; }
int write(const char*, int){ std::cout << "int write(const char* str, int len)\n"; return 0; }
template<std::size_t N = 0, typename = std::enable_if_t<(N > 0)> >
int write(const char (&)[N]) { std::cout << "int write(string-literal) " << N << " \n"; return 0; }
template<typename T>
int write(T&&) = delete;
};
int main(){
char* buf = new char[30];
const char* cbuf = buf;
Writer w;
//w.write(buf); //Fails!
//w.write(cbuf); //Fails!
w.write(buf, 30); //Ok! int write(const char*, int);
w.write(std::string("Haha")); //Ok! int write(std::string&&);
w.write("This is cool"); //Ok! int write(const char (&)[13]);
}
Prints:
int write(const char* str, int len)
int write(std::string)
int write(string-literal) 13
Note that the solution above inherits a disadvantage of "overloading a function with an unconstrained Forwarding Reference". This means that all ICS to the argument type(s) of the viable functions in the overload set will be "deleted"
Upvotes: 5