6

Suppose I have my custom string class:

class my_string : public std::string
{
    // ...
}

And I want to make a templated function which accepts both with my_string by default:

template <typename TString = my_string>
TString do_something(const TString& input)
{
    // ...
}

But when I call it with:

auto result = do_something("abcdef");

It calls (of course) do_something<char[7]>(). How to force it to call do_something<my_string> without explicitly specifying the type (i.e. write do_something("abcdef"), not do_something<my_string>("abcdef")) ?

Barry
  • 286,269
  • 29
  • 621
  • 977
vladon
  • 8,158
  • 2
  • 47
  • 91
  • 1
    Note: Although just an example, never derive from std::string! –  Sep 28 '16 at 11:53
  • 6
    I would just add an overload for `const char[]` and have it forward to the function you want to call. – NathanOliver Sep 28 '16 at 11:54
  • 1
    Literal string suffix might help as alternative: `"abcdef"s` and with `operator "" _my_s` `"abcdef"_my_s`. – Jarod42 Sep 28 '16 at 12:02
  • 1
    Why a template? my_string is a std::string (hence std::string should be convertible to my_string) –  Sep 28 '16 at 12:02
  • @DieterLücking Why not to derive from `std::string`? – vladon Sep 28 '16 at 12:06
  • ["Why should one not derive from c++ std string class?"](http://stackoverflow.com/questions/6006860/why-should-one-not-derive-from-c-std-string-class) –  Sep 28 '16 at 12:07
  • @DieterLücking template because I must accept both, and don't want unneeded converting, suppose old legacy code that uses only `std::string` and does not know about `my_string`, function will return `my_string` to it, and I must refactor old legacy code. – vladon Sep 28 '16 at 12:08
  • 2
    @boris that presumes heap allocation and polymorphic pointers. `new std::string` and `delete std::string` are already anti-patterns, heap-allocating a heap-allocation manager like `std::string` is code smell. In a land of smart pointers and value semantics wrappers (aka modern C++) that advice is relatively obsolete. Smart pointers can store their deleter and should be how the objects are created (no direct calls to `new`), value semantics wrappers either don't call delete or use smart pointers. – Yakk - Adam Nevraumont Sep 28 '16 at 12:32
  • Could you edit the question to clarify what you want the return type to be in this special case? `my_string` or `const char *`? – Aaron McDaid Sep 28 '16 at 14:08

3 Answers3

3

Particularize for "construct from string arguments".

template <typename TString = my_string,
typename std::enable_if<std::is_base_of<std::string, TString>::value>::type = nullptr>
TString do_something(const TString& input)
{
    // ...
}

template <typename ...Args,
typename std::enable_if<std::is_constructible<my_string, Args....>::value>::type = nullptr>
my_string do_something(Args&&... args)
{
    return do_something<my_string>({args});
}
utnapistim
  • 26,809
  • 3
  • 46
  • 82
1

Do you really need a function template? The simple function:

my_string do_something(my_string const& input) { ... }

solves your use-case. You can pass in a my_string or string literal, or even a braced-init-list and everything just works.


Since you need a template for other reasons, you can simply provide an overload for arrays of const char:

template <class TString>
TString do_something(TString const&) { ... }

template <size_t N>
my_string do_something(const char (&arr)[N]) {
    return do_something(my_string{arr, N-1});
}

Note: there's no reason to provide a default for the template parameter TString. There's no way for that default to be meaningfully used.

Barry
  • 286,269
  • 29
  • 621
  • 977
  • 1
    It returns `my_string`, which I don't want in old code that uses `std::string`. – vladon Sep 28 '16 at 12:15
  • Do you really want to take the array by reference here? I would think a `const char[]` parameter would be more useful as you can pass an array or pointer. – NathanOliver Sep 28 '16 at 12:35
  • @NathanOliver I would prefer to just capture string literals. – Barry Sep 28 '16 at 13:08
1

Re

I want to make a templated function which accepts both with my_string by default

#include <string>

template< class Type >
struct Explicit_t_ { using T = Type; };

template< class Type >
using Explicit_ = typename Explicit_t_<Type>::T;

namespace my {
    struct String: std::string { using std::string::string; };
}  // namespace my

template< class Some_string >
auto do_something( Explicit_<Some_string> const& )
    -> Some_string
{ return "Template func!"; }

auto do_something( my::String const& )
    -> my::String
{ return "my::String!"; }

#include <iostream>
using namespace std;
auto main()
    -> int
{
    cout << do_something( "" ) << endl;       // Output "my::String!"
    cout << do_something<std::string>( "" ) << endl;
}

You can let the overload for my::String just forward to the function template, unless you want a more specialized or different implementation.

Cheers and hth. - Alf
  • 142,714
  • 15
  • 209
  • 331