4

explicit can be used on eg. a constructor or conversion function to avoid implicitly calling that constructer/conversion - in short.

Im interested in if it is possible to make a single argument somehow (short of roling a new type) ? perhaps using compiler extensions ?

Using a reference makes copy-construction impossible, so that is a solution, albeit not the one im looking for. It would be neat tool/interface specifier sometimes to be able to force explicit at a function level.

void f(std::string& s); //cannot copy convert into s

void f(const std::string& t);//calling with char* allowed

Specificly for strings, what type should be used to avoid construction from char* ?

darune
  • 10,480
  • 2
  • 24
  • 62

2 Answers2

9

You can't mark the parameter explicit, but what you can do is add an overload that has an rvalue reference parameter and delete it like

void f(const std::string& t);
void f(std::string&&) = delete;

What this will do is it will allow f to accept a std::string, but if an rvalue is created because of an implicit conversion then the rvalue reference overload will be selected instead and you'll get a compiler error about using a deleted function.

The reason this works is that an rvalue reference parameter beats a reference to const in overload resolution, so it will always be called when passing a temporary. It's only after the function is selected that the compiler see that it's deleted so it issues an error.

NathanOliver
  • 171,901
  • 28
  • 288
  • 402
  • Thats interesting, will try that – darune Mar 04 '20 at 17:50
  • What if the purpose is to move from the string - but then only want std::string input ? not eg. char* - just a thought – darune Mar 04 '20 at 17:55
  • Can you explain the process of how passing char* as a parameter to void f(const std::string& t); that it gets convert to an rvalue and ends up using void f(std::string&&) = delete; ? I'm curious to understand this, thanks. – noobius Mar 04 '20 at 17:56
  • 1
    @darune If that is the case you can use `void f(char *) = delete;` like John has in their comment. I suggested this because other things can be implicitly converted to a string so this stops all of those with a single overload. – NathanOliver Mar 04 '20 at 17:56
  • Yeah, I see, I just wanted to hear your thoughts on that too. – darune Mar 04 '20 at 17:58
  • 1
    @noobius The compiler sees that there is `void f(const std::string&)` and `void f(const std::string&&)` as the possible overloads. It looks at `std::string` to see if it can be implicitly created with a `char*`. It finds that it can so then it runs the two functions through overload resolution against that temporary it just created. It picks `void f(const std::string&&)` because it is a better match, and then it sees it is deleted so an erorr is raised. – NathanOliver Mar 04 '20 at 17:59
  • @NathanOliver Thank you for the explanation. – noobius Mar 04 '20 at 18:01
  • @darune: "*What if the purpose is to move from the string - but then only want std::string input ? not eg. char* - just a thought*" Is that a thing worth wanting to do? I don't know why you would want to make your interface forbid an action that is 100% valid. – Nicol Bolas Mar 04 '20 at 18:13
  • @NicolBolas the old interface took a char* and took ownership of that - which was pretty bad semanticly. Then just changing the interface to char*->std::string would leave a memory leak on the caller side. Other cool Ideas are very welcome. – darune Mar 04 '20 at 18:50
  • This of course prevents `f(return_a_string())`, which seems undesirable. – Davis Herring Mar 04 '20 at 19:54
7

With C++20, you can do:

void f(std::same_as<std::string> auto const& s);

With C++17, this is equivalent to:

template <typename T,
    std::enable_if_t<std::is_same_v<T, std::string>, int> = 0>
void f(T const& s);

That is, we have to deduce T to be exactly std::string (which string literals or other char pointers obviously are not).


Note that this rejects types like:

struct D : std::string { };

Which would not require a conversion either. If you want to allow those, that's changing the concept to std::derived_from or the enable_if to use std::is_convertible_v<T*, std::string*>.

Barry
  • 286,269
  • 29
  • 621
  • 977
  • I love how answers with "with C++20 you can..." appear more and more! Great answer. You could also explain the concepts syntax behind that – Victor Mar 04 '20 at 18:13