Interface design: safety of overloaded function taking string and char array

c++, interface

Solution

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 

Demo

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"

Problem

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*`.

Original source

Related problems