Using template to handle string and wstring
I have following two functions:
void bar(const std::string &s)
{
someCFunctionU(s.c_str());
}
void bar(const std::wstring &s)
{
someCFunctionW(s.c_str());
}
Both of these call some C function which accepts const char *
or const wchar_t *
and have U
or W
suffixes respectively. I would like to create a template function to handle both of these cases. I tried following attempt:
template <typename T>
void foo(const std::basic_string<T> &s)
{
if constexpr (std::is_same_v<T, char>)
someCFunctionU(s.c_str());
else
someCFunctionW(s.c_str());
}
But this does not seem to work correctly. If I call:
foo("abc");
this will not compile. Why is that? why a compiler is not able to deduce the proper type T
to char
? Is it possible to create one function which would handle both std::string and std::wstring?
c++ templates c++17 template-meta-programming
add a comment |
I have following two functions:
void bar(const std::string &s)
{
someCFunctionU(s.c_str());
}
void bar(const std::wstring &s)
{
someCFunctionW(s.c_str());
}
Both of these call some C function which accepts const char *
or const wchar_t *
and have U
or W
suffixes respectively. I would like to create a template function to handle both of these cases. I tried following attempt:
template <typename T>
void foo(const std::basic_string<T> &s)
{
if constexpr (std::is_same_v<T, char>)
someCFunctionU(s.c_str());
else
someCFunctionW(s.c_str());
}
But this does not seem to work correctly. If I call:
foo("abc");
this will not compile. Why is that? why a compiler is not able to deduce the proper type T
to char
? Is it possible to create one function which would handle both std::string and std::wstring?
c++ templates c++17 template-meta-programming
"abc"
isconst char*
and not a std::(w)string
– MrTux
Nov 20 '18 at 18:55
2
It's actually aconst char[N]
, but it will decay to aconst char*
– NathanOliver
Nov 20 '18 at 18:55
@NathanOliver Why would it decay? During deduction, the parameter is a reference. And during constructor call, it will use the the overload which takesstd::initializer_list<char>
.
– felix
Nov 20 '18 at 19:24
@felix I didn't mean during deduction. I just mean generally it will decay to achar*
. I should have been more specific.
– NathanOliver
Nov 20 '18 at 19:25
@NathanOliver Yes, sometimes. But not in this context. I don't know which part you mean. Problem is that such decay won't happen here in any part.
– felix
Nov 20 '18 at 19:26
add a comment |
I have following two functions:
void bar(const std::string &s)
{
someCFunctionU(s.c_str());
}
void bar(const std::wstring &s)
{
someCFunctionW(s.c_str());
}
Both of these call some C function which accepts const char *
or const wchar_t *
and have U
or W
suffixes respectively. I would like to create a template function to handle both of these cases. I tried following attempt:
template <typename T>
void foo(const std::basic_string<T> &s)
{
if constexpr (std::is_same_v<T, char>)
someCFunctionU(s.c_str());
else
someCFunctionW(s.c_str());
}
But this does not seem to work correctly. If I call:
foo("abc");
this will not compile. Why is that? why a compiler is not able to deduce the proper type T
to char
? Is it possible to create one function which would handle both std::string and std::wstring?
c++ templates c++17 template-meta-programming
I have following two functions:
void bar(const std::string &s)
{
someCFunctionU(s.c_str());
}
void bar(const std::wstring &s)
{
someCFunctionW(s.c_str());
}
Both of these call some C function which accepts const char *
or const wchar_t *
and have U
or W
suffixes respectively. I would like to create a template function to handle both of these cases. I tried following attempt:
template <typename T>
void foo(const std::basic_string<T> &s)
{
if constexpr (std::is_same_v<T, char>)
someCFunctionU(s.c_str());
else
someCFunctionW(s.c_str());
}
But this does not seem to work correctly. If I call:
foo("abc");
this will not compile. Why is that? why a compiler is not able to deduce the proper type T
to char
? Is it possible to create one function which would handle both std::string and std::wstring?
c++ templates c++17 template-meta-programming
c++ templates c++17 template-meta-programming
asked Nov 20 '18 at 18:53
IgorIgor
572411
572411
"abc"
isconst char*
and not a std::(w)string
– MrTux
Nov 20 '18 at 18:55
2
It's actually aconst char[N]
, but it will decay to aconst char*
– NathanOliver
Nov 20 '18 at 18:55
@NathanOliver Why would it decay? During deduction, the parameter is a reference. And during constructor call, it will use the the overload which takesstd::initializer_list<char>
.
– felix
Nov 20 '18 at 19:24
@felix I didn't mean during deduction. I just mean generally it will decay to achar*
. I should have been more specific.
– NathanOliver
Nov 20 '18 at 19:25
@NathanOliver Yes, sometimes. But not in this context. I don't know which part you mean. Problem is that such decay won't happen here in any part.
– felix
Nov 20 '18 at 19:26
add a comment |
"abc"
isconst char*
and not a std::(w)string
– MrTux
Nov 20 '18 at 18:55
2
It's actually aconst char[N]
, but it will decay to aconst char*
– NathanOliver
Nov 20 '18 at 18:55
@NathanOliver Why would it decay? During deduction, the parameter is a reference. And during constructor call, it will use the the overload which takesstd::initializer_list<char>
.
– felix
Nov 20 '18 at 19:24
@felix I didn't mean during deduction. I just mean generally it will decay to achar*
. I should have been more specific.
– NathanOliver
Nov 20 '18 at 19:25
@NathanOliver Yes, sometimes. But not in this context. I don't know which part you mean. Problem is that such decay won't happen here in any part.
– felix
Nov 20 '18 at 19:26
"abc"
is const char*
and not a std::(w)string– MrTux
Nov 20 '18 at 18:55
"abc"
is const char*
and not a std::(w)string– MrTux
Nov 20 '18 at 18:55
2
2
It's actually a
const char[N]
, but it will decay to a const char*
– NathanOliver
Nov 20 '18 at 18:55
It's actually a
const char[N]
, but it will decay to a const char*
– NathanOliver
Nov 20 '18 at 18:55
@NathanOliver Why would it decay? During deduction, the parameter is a reference. And during constructor call, it will use the the overload which takes
std::initializer_list<char>
.– felix
Nov 20 '18 at 19:24
@NathanOliver Why would it decay? During deduction, the parameter is a reference. And during constructor call, it will use the the overload which takes
std::initializer_list<char>
.– felix
Nov 20 '18 at 19:24
@felix I didn't mean during deduction. I just mean generally it will decay to a
char*
. I should have been more specific.– NathanOliver
Nov 20 '18 at 19:25
@felix I didn't mean during deduction. I just mean generally it will decay to a
char*
. I should have been more specific.– NathanOliver
Nov 20 '18 at 19:25
@NathanOliver Yes, sometimes. But not in this context. I don't know which part you mean. Problem is that such decay won't happen here in any part.
– felix
Nov 20 '18 at 19:26
@NathanOliver Yes, sometimes. But not in this context. I don't know which part you mean. Problem is that such decay won't happen here in any part.
– felix
Nov 20 '18 at 19:26
add a comment |
4 Answers
4
active
oldest
votes
this will not compile. Why is that? why a compiler is not able to deduce the proper type T to char?
As better explained by others, "abc"
is a char[4]
, so is convertible to a std::basic_string<char>
but isn't a std::basic_string<char>
, so can't be deduced the T
type as char
for a template function that accept a std::basic_string<T>
.
Is it possible to create one function which would handle both std::string and std::wstring?
Yes, it's possible; but what's wrong with your two-function-in-overloading solution?
Anyway, if you really want a single function and if you accept to write a lot of casuistry, I suppose you can write something as follows
template <typename T>
void foo (T const & s)
{
if constexpr ( std::is_same_v<T, std::string> )
someCFunctionU(s.c_str());
else if constexpr ( std::is_convertible_v<T, char const *>)
someCFunctionU(s);
else if constexpr ( std::is_same_v<T, std::wstring> )
someCFunctionW(s.c_str());
else if constexpr ( std::is_convertible_v<T, wchar_t const *> )
someCFunctionW(s);
// else exception ?
}
or, a little more synthetic but less efficient
template <typename T>
void foo (T const & s)
{
if constexpr ( std::is_convertible_v<T, std::string> )
someCFunctionU(std::string{s}.c_str());
else if constexpr (std::is_convertible_v<T, std::wstring> )
someCFunctionW(std::wstring{s}.c_str());
// else exception ?
}
So you should be able to call foo()
with std::string
, std::wstring
, char *
, wchar_t *
, char
or wchar_t
.
add a comment |
The issue here is that in foo("abc");
, "abc"
is not a std::string
or a std::wstring
, it is a const char[N]
. Since it isn't a std::string
or a std::wstring
the compiler cannot deduce what T
should be and it fails to compile. The easiest solution is to use what you already have. The overloads will be considered and it is a better match to convert "abc"
to a std::string
so it will call that version of the function.
If you want you could use a std::string_view
/std::wstring_view
instead of std::string
/std::wstring
so you don't actually allocate any memory if you pass the function a string literal. That would change the overloads to
void bar(std::string_view s)
{
someCFunctionU(s.data());
}
void bar(std::wstring_view s)
{
someCFunctionW(s.data());
}
Do note that std::basic_string_view
can be constructed without having a null terminator so it is possible to pass a std::basic_string_view
that won't fulfill the null terminated c-string requirement that your C function has. In that case the code has undefined behavior.
Hmm.string_view
can't guarantee a null-terminated string, so passings.data()
to the C-style function is dangerous and should probably be avoided, since that C-style function expects a null-terminated string. If the C-style function could be changed to take a pointer and a length, all would be fine. Alternatively, there may be some non-standardstring_view
implementations which guarantee null-termination, meaning that they can only be at the end of a string
– Justin
Nov 20 '18 at 19:11
1
@Justin That really upsets me withstring_view
that we can't do this. They give me this nice shinny thing but then rip the carpet out from under my feet. I'll put a warning in the answer for the OP, just so they know, but it should be okay if the only feed strings/c-strings to the function.
– NathanOliver
Nov 20 '18 at 19:15
string_view
with guaranteed null-termination is not as useful. It means that thestring_view
can only point to the end of strings, rather than any substring.
– Justin
Nov 20 '18 at 19:16
1
@Justin That's why I really wanted aslice
class. I always envisioned astring_view
as a read only view of a string with all the member functions. Aslice
orstring_slice
would have the non-guarantees.
– NathanOliver
Nov 20 '18 at 19:19
add a comment |
A workaround in C++17 is:
template <typename T>
void foo(const T &s)
{
std::basic_string_view sv{s}; // Class template argument deduction
if constexpr (std::is_same_v<typename decltype(sv)::value_type, char>)
someCFunctionU(sv.data());
else
someCFunctionW(sv.data());
}
And to avoid issue mentioned by Justin about non-null-terminated string
template <typename T> struct is_basic_string_view : std::false_type {};
template <typename T> struct is_basic_string_view<basic_string_view<T>> : std::true_type
{};
template <typename T>
std::enable_if_t<!is_basic_string_view<T>::value> foo(const T &s)
{
std::basic_string_view sv{s}; // Class template argument deduction
if constexpr (std::is_same_v<typename decltype(sv)::value_type, char>)
someCFunctionU(sv.data());
else
someCFunctionW(sv.data());
}
This has the same danger I mentioned in NathanOliver's answer: if you pass a non-null-terminatedstring_view
tofoo(...)
, this will accept it, passing a non-null-terminated string to the C functions, which is likely to cause problems.
– Justin
Nov 20 '18 at 19:15
1
@Justin: Safer version added.
– Jarod42
Nov 20 '18 at 19:21
add a comment |
Yes, there exist a type, i.e. std::basic_string<char>
, which can be copy initialized from expression "abc"
. So you can call a function like void foo(std::basic_string<char>)
with argument "abc"
.
And no, you can't call a function template template <class T> void foo(const std::basic_string<T> &s)
with argument "abc"
. Because in order to figure out whether the parameter can be initialized by the argument, the compiler need to determine the template parameter T
first. It will try to match const std::basic_string<T> &
against const char [4]
. And it will fail.
The reason why it will fail is because of the template argument deduction rule. The actual rule is very complicated. But in this case, for std::basic_string<char>
to be examined during the deduction, compiler will need to look for a proper "converting constructor", i.e. the constructor which can be called implicitly with argument "abc"
, and such lookup isn't allowed by the standard during deduction.
Yes, it is possible to handle std::string and std::wstring in one function template:
void foo_impl(const std::string &) {}
void foo_impl(const std::wstring &) {}
template <class T>
auto foo(T &&t) {
return foo_impl(std::forward<T>(t));
}
std::basic_string<char>
is exactly the same asstd::string
– Justin
Nov 20 '18 at 19:08
@Justin Yes, std::string is its alias. But the original question is using std::basic_string. And in this context, using std::string is inconvenient.
– felix
Nov 20 '18 at 19:15
add a comment |
Your Answer
StackExchange.ifUsing("editor", function () {
StackExchange.using("externalEditor", function () {
StackExchange.using("snippets", function () {
StackExchange.snippets.init();
});
});
}, "code-snippets");
StackExchange.ready(function() {
var channelOptions = {
tags: "".split(" "),
id: "1"
};
initTagRenderer("".split(" "), "".split(" "), channelOptions);
StackExchange.using("externalEditor", function() {
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled) {
StackExchange.using("snippets", function() {
createEditor();
});
}
else {
createEditor();
}
});
function createEditor() {
StackExchange.prepareEditor({
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: true,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: 10,
bindNavPrevention: true,
postfix: "",
imageUploader: {
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
},
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
});
}
});
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53399688%2fusing-template-to-handle-string-and-wstring%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
4 Answers
4
active
oldest
votes
4 Answers
4
active
oldest
votes
active
oldest
votes
active
oldest
votes
this will not compile. Why is that? why a compiler is not able to deduce the proper type T to char?
As better explained by others, "abc"
is a char[4]
, so is convertible to a std::basic_string<char>
but isn't a std::basic_string<char>
, so can't be deduced the T
type as char
for a template function that accept a std::basic_string<T>
.
Is it possible to create one function which would handle both std::string and std::wstring?
Yes, it's possible; but what's wrong with your two-function-in-overloading solution?
Anyway, if you really want a single function and if you accept to write a lot of casuistry, I suppose you can write something as follows
template <typename T>
void foo (T const & s)
{
if constexpr ( std::is_same_v<T, std::string> )
someCFunctionU(s.c_str());
else if constexpr ( std::is_convertible_v<T, char const *>)
someCFunctionU(s);
else if constexpr ( std::is_same_v<T, std::wstring> )
someCFunctionW(s.c_str());
else if constexpr ( std::is_convertible_v<T, wchar_t const *> )
someCFunctionW(s);
// else exception ?
}
or, a little more synthetic but less efficient
template <typename T>
void foo (T const & s)
{
if constexpr ( std::is_convertible_v<T, std::string> )
someCFunctionU(std::string{s}.c_str());
else if constexpr (std::is_convertible_v<T, std::wstring> )
someCFunctionW(std::wstring{s}.c_str());
// else exception ?
}
So you should be able to call foo()
with std::string
, std::wstring
, char *
, wchar_t *
, char
or wchar_t
.
add a comment |
this will not compile. Why is that? why a compiler is not able to deduce the proper type T to char?
As better explained by others, "abc"
is a char[4]
, so is convertible to a std::basic_string<char>
but isn't a std::basic_string<char>
, so can't be deduced the T
type as char
for a template function that accept a std::basic_string<T>
.
Is it possible to create one function which would handle both std::string and std::wstring?
Yes, it's possible; but what's wrong with your two-function-in-overloading solution?
Anyway, if you really want a single function and if you accept to write a lot of casuistry, I suppose you can write something as follows
template <typename T>
void foo (T const & s)
{
if constexpr ( std::is_same_v<T, std::string> )
someCFunctionU(s.c_str());
else if constexpr ( std::is_convertible_v<T, char const *>)
someCFunctionU(s);
else if constexpr ( std::is_same_v<T, std::wstring> )
someCFunctionW(s.c_str());
else if constexpr ( std::is_convertible_v<T, wchar_t const *> )
someCFunctionW(s);
// else exception ?
}
or, a little more synthetic but less efficient
template <typename T>
void foo (T const & s)
{
if constexpr ( std::is_convertible_v<T, std::string> )
someCFunctionU(std::string{s}.c_str());
else if constexpr (std::is_convertible_v<T, std::wstring> )
someCFunctionW(std::wstring{s}.c_str());
// else exception ?
}
So you should be able to call foo()
with std::string
, std::wstring
, char *
, wchar_t *
, char
or wchar_t
.
add a comment |
this will not compile. Why is that? why a compiler is not able to deduce the proper type T to char?
As better explained by others, "abc"
is a char[4]
, so is convertible to a std::basic_string<char>
but isn't a std::basic_string<char>
, so can't be deduced the T
type as char
for a template function that accept a std::basic_string<T>
.
Is it possible to create one function which would handle both std::string and std::wstring?
Yes, it's possible; but what's wrong with your two-function-in-overloading solution?
Anyway, if you really want a single function and if you accept to write a lot of casuistry, I suppose you can write something as follows
template <typename T>
void foo (T const & s)
{
if constexpr ( std::is_same_v<T, std::string> )
someCFunctionU(s.c_str());
else if constexpr ( std::is_convertible_v<T, char const *>)
someCFunctionU(s);
else if constexpr ( std::is_same_v<T, std::wstring> )
someCFunctionW(s.c_str());
else if constexpr ( std::is_convertible_v<T, wchar_t const *> )
someCFunctionW(s);
// else exception ?
}
or, a little more synthetic but less efficient
template <typename T>
void foo (T const & s)
{
if constexpr ( std::is_convertible_v<T, std::string> )
someCFunctionU(std::string{s}.c_str());
else if constexpr (std::is_convertible_v<T, std::wstring> )
someCFunctionW(std::wstring{s}.c_str());
// else exception ?
}
So you should be able to call foo()
with std::string
, std::wstring
, char *
, wchar_t *
, char
or wchar_t
.
this will not compile. Why is that? why a compiler is not able to deduce the proper type T to char?
As better explained by others, "abc"
is a char[4]
, so is convertible to a std::basic_string<char>
but isn't a std::basic_string<char>
, so can't be deduced the T
type as char
for a template function that accept a std::basic_string<T>
.
Is it possible to create one function which would handle both std::string and std::wstring?
Yes, it's possible; but what's wrong with your two-function-in-overloading solution?
Anyway, if you really want a single function and if you accept to write a lot of casuistry, I suppose you can write something as follows
template <typename T>
void foo (T const & s)
{
if constexpr ( std::is_same_v<T, std::string> )
someCFunctionU(s.c_str());
else if constexpr ( std::is_convertible_v<T, char const *>)
someCFunctionU(s);
else if constexpr ( std::is_same_v<T, std::wstring> )
someCFunctionW(s.c_str());
else if constexpr ( std::is_convertible_v<T, wchar_t const *> )
someCFunctionW(s);
// else exception ?
}
or, a little more synthetic but less efficient
template <typename T>
void foo (T const & s)
{
if constexpr ( std::is_convertible_v<T, std::string> )
someCFunctionU(std::string{s}.c_str());
else if constexpr (std::is_convertible_v<T, std::wstring> )
someCFunctionW(std::wstring{s}.c_str());
// else exception ?
}
So you should be able to call foo()
with std::string
, std::wstring
, char *
, wchar_t *
, char
or wchar_t
.
edited Nov 20 '18 at 19:33
answered Nov 20 '18 at 19:27
max66max66
35.9k74165
35.9k74165
add a comment |
add a comment |
The issue here is that in foo("abc");
, "abc"
is not a std::string
or a std::wstring
, it is a const char[N]
. Since it isn't a std::string
or a std::wstring
the compiler cannot deduce what T
should be and it fails to compile. The easiest solution is to use what you already have. The overloads will be considered and it is a better match to convert "abc"
to a std::string
so it will call that version of the function.
If you want you could use a std::string_view
/std::wstring_view
instead of std::string
/std::wstring
so you don't actually allocate any memory if you pass the function a string literal. That would change the overloads to
void bar(std::string_view s)
{
someCFunctionU(s.data());
}
void bar(std::wstring_view s)
{
someCFunctionW(s.data());
}
Do note that std::basic_string_view
can be constructed without having a null terminator so it is possible to pass a std::basic_string_view
that won't fulfill the null terminated c-string requirement that your C function has. In that case the code has undefined behavior.
Hmm.string_view
can't guarantee a null-terminated string, so passings.data()
to the C-style function is dangerous and should probably be avoided, since that C-style function expects a null-terminated string. If the C-style function could be changed to take a pointer and a length, all would be fine. Alternatively, there may be some non-standardstring_view
implementations which guarantee null-termination, meaning that they can only be at the end of a string
– Justin
Nov 20 '18 at 19:11
1
@Justin That really upsets me withstring_view
that we can't do this. They give me this nice shinny thing but then rip the carpet out from under my feet. I'll put a warning in the answer for the OP, just so they know, but it should be okay if the only feed strings/c-strings to the function.
– NathanOliver
Nov 20 '18 at 19:15
string_view
with guaranteed null-termination is not as useful. It means that thestring_view
can only point to the end of strings, rather than any substring.
– Justin
Nov 20 '18 at 19:16
1
@Justin That's why I really wanted aslice
class. I always envisioned astring_view
as a read only view of a string with all the member functions. Aslice
orstring_slice
would have the non-guarantees.
– NathanOliver
Nov 20 '18 at 19:19
add a comment |
The issue here is that in foo("abc");
, "abc"
is not a std::string
or a std::wstring
, it is a const char[N]
. Since it isn't a std::string
or a std::wstring
the compiler cannot deduce what T
should be and it fails to compile. The easiest solution is to use what you already have. The overloads will be considered and it is a better match to convert "abc"
to a std::string
so it will call that version of the function.
If you want you could use a std::string_view
/std::wstring_view
instead of std::string
/std::wstring
so you don't actually allocate any memory if you pass the function a string literal. That would change the overloads to
void bar(std::string_view s)
{
someCFunctionU(s.data());
}
void bar(std::wstring_view s)
{
someCFunctionW(s.data());
}
Do note that std::basic_string_view
can be constructed without having a null terminator so it is possible to pass a std::basic_string_view
that won't fulfill the null terminated c-string requirement that your C function has. In that case the code has undefined behavior.
Hmm.string_view
can't guarantee a null-terminated string, so passings.data()
to the C-style function is dangerous and should probably be avoided, since that C-style function expects a null-terminated string. If the C-style function could be changed to take a pointer and a length, all would be fine. Alternatively, there may be some non-standardstring_view
implementations which guarantee null-termination, meaning that they can only be at the end of a string
– Justin
Nov 20 '18 at 19:11
1
@Justin That really upsets me withstring_view
that we can't do this. They give me this nice shinny thing but then rip the carpet out from under my feet. I'll put a warning in the answer for the OP, just so they know, but it should be okay if the only feed strings/c-strings to the function.
– NathanOliver
Nov 20 '18 at 19:15
string_view
with guaranteed null-termination is not as useful. It means that thestring_view
can only point to the end of strings, rather than any substring.
– Justin
Nov 20 '18 at 19:16
1
@Justin That's why I really wanted aslice
class. I always envisioned astring_view
as a read only view of a string with all the member functions. Aslice
orstring_slice
would have the non-guarantees.
– NathanOliver
Nov 20 '18 at 19:19
add a comment |
The issue here is that in foo("abc");
, "abc"
is not a std::string
or a std::wstring
, it is a const char[N]
. Since it isn't a std::string
or a std::wstring
the compiler cannot deduce what T
should be and it fails to compile. The easiest solution is to use what you already have. The overloads will be considered and it is a better match to convert "abc"
to a std::string
so it will call that version of the function.
If you want you could use a std::string_view
/std::wstring_view
instead of std::string
/std::wstring
so you don't actually allocate any memory if you pass the function a string literal. That would change the overloads to
void bar(std::string_view s)
{
someCFunctionU(s.data());
}
void bar(std::wstring_view s)
{
someCFunctionW(s.data());
}
Do note that std::basic_string_view
can be constructed without having a null terminator so it is possible to pass a std::basic_string_view
that won't fulfill the null terminated c-string requirement that your C function has. In that case the code has undefined behavior.
The issue here is that in foo("abc");
, "abc"
is not a std::string
or a std::wstring
, it is a const char[N]
. Since it isn't a std::string
or a std::wstring
the compiler cannot deduce what T
should be and it fails to compile. The easiest solution is to use what you already have. The overloads will be considered and it is a better match to convert "abc"
to a std::string
so it will call that version of the function.
If you want you could use a std::string_view
/std::wstring_view
instead of std::string
/std::wstring
so you don't actually allocate any memory if you pass the function a string literal. That would change the overloads to
void bar(std::string_view s)
{
someCFunctionU(s.data());
}
void bar(std::wstring_view s)
{
someCFunctionW(s.data());
}
Do note that std::basic_string_view
can be constructed without having a null terminator so it is possible to pass a std::basic_string_view
that won't fulfill the null terminated c-string requirement that your C function has. In that case the code has undefined behavior.
edited Nov 20 '18 at 19:17
answered Nov 20 '18 at 19:02
NathanOliverNathanOliver
90.5k15126190
90.5k15126190
Hmm.string_view
can't guarantee a null-terminated string, so passings.data()
to the C-style function is dangerous and should probably be avoided, since that C-style function expects a null-terminated string. If the C-style function could be changed to take a pointer and a length, all would be fine. Alternatively, there may be some non-standardstring_view
implementations which guarantee null-termination, meaning that they can only be at the end of a string
– Justin
Nov 20 '18 at 19:11
1
@Justin That really upsets me withstring_view
that we can't do this. They give me this nice shinny thing but then rip the carpet out from under my feet. I'll put a warning in the answer for the OP, just so they know, but it should be okay if the only feed strings/c-strings to the function.
– NathanOliver
Nov 20 '18 at 19:15
string_view
with guaranteed null-termination is not as useful. It means that thestring_view
can only point to the end of strings, rather than any substring.
– Justin
Nov 20 '18 at 19:16
1
@Justin That's why I really wanted aslice
class. I always envisioned astring_view
as a read only view of a string with all the member functions. Aslice
orstring_slice
would have the non-guarantees.
– NathanOliver
Nov 20 '18 at 19:19
add a comment |
Hmm.string_view
can't guarantee a null-terminated string, so passings.data()
to the C-style function is dangerous and should probably be avoided, since that C-style function expects a null-terminated string. If the C-style function could be changed to take a pointer and a length, all would be fine. Alternatively, there may be some non-standardstring_view
implementations which guarantee null-termination, meaning that they can only be at the end of a string
– Justin
Nov 20 '18 at 19:11
1
@Justin That really upsets me withstring_view
that we can't do this. They give me this nice shinny thing but then rip the carpet out from under my feet. I'll put a warning in the answer for the OP, just so they know, but it should be okay if the only feed strings/c-strings to the function.
– NathanOliver
Nov 20 '18 at 19:15
string_view
with guaranteed null-termination is not as useful. It means that thestring_view
can only point to the end of strings, rather than any substring.
– Justin
Nov 20 '18 at 19:16
1
@Justin That's why I really wanted aslice
class. I always envisioned astring_view
as a read only view of a string with all the member functions. Aslice
orstring_slice
would have the non-guarantees.
– NathanOliver
Nov 20 '18 at 19:19
Hmm.
string_view
can't guarantee a null-terminated string, so passing s.data()
to the C-style function is dangerous and should probably be avoided, since that C-style function expects a null-terminated string. If the C-style function could be changed to take a pointer and a length, all would be fine. Alternatively, there may be some non-standard string_view
implementations which guarantee null-termination, meaning that they can only be at the end of a string– Justin
Nov 20 '18 at 19:11
Hmm.
string_view
can't guarantee a null-terminated string, so passing s.data()
to the C-style function is dangerous and should probably be avoided, since that C-style function expects a null-terminated string. If the C-style function could be changed to take a pointer and a length, all would be fine. Alternatively, there may be some non-standard string_view
implementations which guarantee null-termination, meaning that they can only be at the end of a string– Justin
Nov 20 '18 at 19:11
1
1
@Justin That really upsets me with
string_view
that we can't do this. They give me this nice shinny thing but then rip the carpet out from under my feet. I'll put a warning in the answer for the OP, just so they know, but it should be okay if the only feed strings/c-strings to the function.– NathanOliver
Nov 20 '18 at 19:15
@Justin That really upsets me with
string_view
that we can't do this. They give me this nice shinny thing but then rip the carpet out from under my feet. I'll put a warning in the answer for the OP, just so they know, but it should be okay if the only feed strings/c-strings to the function.– NathanOliver
Nov 20 '18 at 19:15
string_view
with guaranteed null-termination is not as useful. It means that the string_view
can only point to the end of strings, rather than any substring.– Justin
Nov 20 '18 at 19:16
string_view
with guaranteed null-termination is not as useful. It means that the string_view
can only point to the end of strings, rather than any substring.– Justin
Nov 20 '18 at 19:16
1
1
@Justin That's why I really wanted a
slice
class. I always envisioned a string_view
as a read only view of a string with all the member functions. A slice
or string_slice
would have the non-guarantees.– NathanOliver
Nov 20 '18 at 19:19
@Justin That's why I really wanted a
slice
class. I always envisioned a string_view
as a read only view of a string with all the member functions. A slice
or string_slice
would have the non-guarantees.– NathanOliver
Nov 20 '18 at 19:19
add a comment |
A workaround in C++17 is:
template <typename T>
void foo(const T &s)
{
std::basic_string_view sv{s}; // Class template argument deduction
if constexpr (std::is_same_v<typename decltype(sv)::value_type, char>)
someCFunctionU(sv.data());
else
someCFunctionW(sv.data());
}
And to avoid issue mentioned by Justin about non-null-terminated string
template <typename T> struct is_basic_string_view : std::false_type {};
template <typename T> struct is_basic_string_view<basic_string_view<T>> : std::true_type
{};
template <typename T>
std::enable_if_t<!is_basic_string_view<T>::value> foo(const T &s)
{
std::basic_string_view sv{s}; // Class template argument deduction
if constexpr (std::is_same_v<typename decltype(sv)::value_type, char>)
someCFunctionU(sv.data());
else
someCFunctionW(sv.data());
}
This has the same danger I mentioned in NathanOliver's answer: if you pass a non-null-terminatedstring_view
tofoo(...)
, this will accept it, passing a non-null-terminated string to the C functions, which is likely to cause problems.
– Justin
Nov 20 '18 at 19:15
1
@Justin: Safer version added.
– Jarod42
Nov 20 '18 at 19:21
add a comment |
A workaround in C++17 is:
template <typename T>
void foo(const T &s)
{
std::basic_string_view sv{s}; // Class template argument deduction
if constexpr (std::is_same_v<typename decltype(sv)::value_type, char>)
someCFunctionU(sv.data());
else
someCFunctionW(sv.data());
}
And to avoid issue mentioned by Justin about non-null-terminated string
template <typename T> struct is_basic_string_view : std::false_type {};
template <typename T> struct is_basic_string_view<basic_string_view<T>> : std::true_type
{};
template <typename T>
std::enable_if_t<!is_basic_string_view<T>::value> foo(const T &s)
{
std::basic_string_view sv{s}; // Class template argument deduction
if constexpr (std::is_same_v<typename decltype(sv)::value_type, char>)
someCFunctionU(sv.data());
else
someCFunctionW(sv.data());
}
This has the same danger I mentioned in NathanOliver's answer: if you pass a non-null-terminatedstring_view
tofoo(...)
, this will accept it, passing a non-null-terminated string to the C functions, which is likely to cause problems.
– Justin
Nov 20 '18 at 19:15
1
@Justin: Safer version added.
– Jarod42
Nov 20 '18 at 19:21
add a comment |
A workaround in C++17 is:
template <typename T>
void foo(const T &s)
{
std::basic_string_view sv{s}; // Class template argument deduction
if constexpr (std::is_same_v<typename decltype(sv)::value_type, char>)
someCFunctionU(sv.data());
else
someCFunctionW(sv.data());
}
And to avoid issue mentioned by Justin about non-null-terminated string
template <typename T> struct is_basic_string_view : std::false_type {};
template <typename T> struct is_basic_string_view<basic_string_view<T>> : std::true_type
{};
template <typename T>
std::enable_if_t<!is_basic_string_view<T>::value> foo(const T &s)
{
std::basic_string_view sv{s}; // Class template argument deduction
if constexpr (std::is_same_v<typename decltype(sv)::value_type, char>)
someCFunctionU(sv.data());
else
someCFunctionW(sv.data());
}
A workaround in C++17 is:
template <typename T>
void foo(const T &s)
{
std::basic_string_view sv{s}; // Class template argument deduction
if constexpr (std::is_same_v<typename decltype(sv)::value_type, char>)
someCFunctionU(sv.data());
else
someCFunctionW(sv.data());
}
And to avoid issue mentioned by Justin about non-null-terminated string
template <typename T> struct is_basic_string_view : std::false_type {};
template <typename T> struct is_basic_string_view<basic_string_view<T>> : std::true_type
{};
template <typename T>
std::enable_if_t<!is_basic_string_view<T>::value> foo(const T &s)
{
std::basic_string_view sv{s}; // Class template argument deduction
if constexpr (std::is_same_v<typename decltype(sv)::value_type, char>)
someCFunctionU(sv.data());
else
someCFunctionW(sv.data());
}
edited Nov 20 '18 at 19:21
answered Nov 20 '18 at 19:13
Jarod42Jarod42
115k12102182
115k12102182
This has the same danger I mentioned in NathanOliver's answer: if you pass a non-null-terminatedstring_view
tofoo(...)
, this will accept it, passing a non-null-terminated string to the C functions, which is likely to cause problems.
– Justin
Nov 20 '18 at 19:15
1
@Justin: Safer version added.
– Jarod42
Nov 20 '18 at 19:21
add a comment |
This has the same danger I mentioned in NathanOliver's answer: if you pass a non-null-terminatedstring_view
tofoo(...)
, this will accept it, passing a non-null-terminated string to the C functions, which is likely to cause problems.
– Justin
Nov 20 '18 at 19:15
1
@Justin: Safer version added.
– Jarod42
Nov 20 '18 at 19:21
This has the same danger I mentioned in NathanOliver's answer: if you pass a non-null-terminated
string_view
to foo(...)
, this will accept it, passing a non-null-terminated string to the C functions, which is likely to cause problems.– Justin
Nov 20 '18 at 19:15
This has the same danger I mentioned in NathanOliver's answer: if you pass a non-null-terminated
string_view
to foo(...)
, this will accept it, passing a non-null-terminated string to the C functions, which is likely to cause problems.– Justin
Nov 20 '18 at 19:15
1
1
@Justin: Safer version added.
– Jarod42
Nov 20 '18 at 19:21
@Justin: Safer version added.
– Jarod42
Nov 20 '18 at 19:21
add a comment |
Yes, there exist a type, i.e. std::basic_string<char>
, which can be copy initialized from expression "abc"
. So you can call a function like void foo(std::basic_string<char>)
with argument "abc"
.
And no, you can't call a function template template <class T> void foo(const std::basic_string<T> &s)
with argument "abc"
. Because in order to figure out whether the parameter can be initialized by the argument, the compiler need to determine the template parameter T
first. It will try to match const std::basic_string<T> &
against const char [4]
. And it will fail.
The reason why it will fail is because of the template argument deduction rule. The actual rule is very complicated. But in this case, for std::basic_string<char>
to be examined during the deduction, compiler will need to look for a proper "converting constructor", i.e. the constructor which can be called implicitly with argument "abc"
, and such lookup isn't allowed by the standard during deduction.
Yes, it is possible to handle std::string and std::wstring in one function template:
void foo_impl(const std::string &) {}
void foo_impl(const std::wstring &) {}
template <class T>
auto foo(T &&t) {
return foo_impl(std::forward<T>(t));
}
std::basic_string<char>
is exactly the same asstd::string
– Justin
Nov 20 '18 at 19:08
@Justin Yes, std::string is its alias. But the original question is using std::basic_string. And in this context, using std::string is inconvenient.
– felix
Nov 20 '18 at 19:15
add a comment |
Yes, there exist a type, i.e. std::basic_string<char>
, which can be copy initialized from expression "abc"
. So you can call a function like void foo(std::basic_string<char>)
with argument "abc"
.
And no, you can't call a function template template <class T> void foo(const std::basic_string<T> &s)
with argument "abc"
. Because in order to figure out whether the parameter can be initialized by the argument, the compiler need to determine the template parameter T
first. It will try to match const std::basic_string<T> &
against const char [4]
. And it will fail.
The reason why it will fail is because of the template argument deduction rule. The actual rule is very complicated. But in this case, for std::basic_string<char>
to be examined during the deduction, compiler will need to look for a proper "converting constructor", i.e. the constructor which can be called implicitly with argument "abc"
, and such lookup isn't allowed by the standard during deduction.
Yes, it is possible to handle std::string and std::wstring in one function template:
void foo_impl(const std::string &) {}
void foo_impl(const std::wstring &) {}
template <class T>
auto foo(T &&t) {
return foo_impl(std::forward<T>(t));
}
std::basic_string<char>
is exactly the same asstd::string
– Justin
Nov 20 '18 at 19:08
@Justin Yes, std::string is its alias. But the original question is using std::basic_string. And in this context, using std::string is inconvenient.
– felix
Nov 20 '18 at 19:15
add a comment |
Yes, there exist a type, i.e. std::basic_string<char>
, which can be copy initialized from expression "abc"
. So you can call a function like void foo(std::basic_string<char>)
with argument "abc"
.
And no, you can't call a function template template <class T> void foo(const std::basic_string<T> &s)
with argument "abc"
. Because in order to figure out whether the parameter can be initialized by the argument, the compiler need to determine the template parameter T
first. It will try to match const std::basic_string<T> &
against const char [4]
. And it will fail.
The reason why it will fail is because of the template argument deduction rule. The actual rule is very complicated. But in this case, for std::basic_string<char>
to be examined during the deduction, compiler will need to look for a proper "converting constructor", i.e. the constructor which can be called implicitly with argument "abc"
, and such lookup isn't allowed by the standard during deduction.
Yes, it is possible to handle std::string and std::wstring in one function template:
void foo_impl(const std::string &) {}
void foo_impl(const std::wstring &) {}
template <class T>
auto foo(T &&t) {
return foo_impl(std::forward<T>(t));
}
Yes, there exist a type, i.e. std::basic_string<char>
, which can be copy initialized from expression "abc"
. So you can call a function like void foo(std::basic_string<char>)
with argument "abc"
.
And no, you can't call a function template template <class T> void foo(const std::basic_string<T> &s)
with argument "abc"
. Because in order to figure out whether the parameter can be initialized by the argument, the compiler need to determine the template parameter T
first. It will try to match const std::basic_string<T> &
against const char [4]
. And it will fail.
The reason why it will fail is because of the template argument deduction rule. The actual rule is very complicated. But in this case, for std::basic_string<char>
to be examined during the deduction, compiler will need to look for a proper "converting constructor", i.e. the constructor which can be called implicitly with argument "abc"
, and such lookup isn't allowed by the standard during deduction.
Yes, it is possible to handle std::string and std::wstring in one function template:
void foo_impl(const std::string &) {}
void foo_impl(const std::wstring &) {}
template <class T>
auto foo(T &&t) {
return foo_impl(std::forward<T>(t));
}
edited Nov 20 '18 at 19:33
answered Nov 20 '18 at 19:05
felixfelix
1,473314
1,473314
std::basic_string<char>
is exactly the same asstd::string
– Justin
Nov 20 '18 at 19:08
@Justin Yes, std::string is its alias. But the original question is using std::basic_string. And in this context, using std::string is inconvenient.
– felix
Nov 20 '18 at 19:15
add a comment |
std::basic_string<char>
is exactly the same asstd::string
– Justin
Nov 20 '18 at 19:08
@Justin Yes, std::string is its alias. But the original question is using std::basic_string. And in this context, using std::string is inconvenient.
– felix
Nov 20 '18 at 19:15
std::basic_string<char>
is exactly the same as std::string
– Justin
Nov 20 '18 at 19:08
std::basic_string<char>
is exactly the same as std::string
– Justin
Nov 20 '18 at 19:08
@Justin Yes, std::string is its alias. But the original question is using std::basic_string. And in this context, using std::string is inconvenient.
– felix
Nov 20 '18 at 19:15
@Justin Yes, std::string is its alias. But the original question is using std::basic_string. And in this context, using std::string is inconvenient.
– felix
Nov 20 '18 at 19:15
add a comment |
Thanks for contributing an answer to Stack Overflow!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53399688%2fusing-template-to-handle-string-and-wstring%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
"abc"
isconst char*
and not a std::(w)string– MrTux
Nov 20 '18 at 18:55
2
It's actually a
const char[N]
, but it will decay to aconst char*
– NathanOliver
Nov 20 '18 at 18:55
@NathanOliver Why would it decay? During deduction, the parameter is a reference. And during constructor call, it will use the the overload which takes
std::initializer_list<char>
.– felix
Nov 20 '18 at 19:24
@felix I didn't mean during deduction. I just mean generally it will decay to a
char*
. I should have been more specific.– NathanOliver
Nov 20 '18 at 19:25
@NathanOliver Yes, sometimes. But not in this context. I don't know which part you mean. Problem is that such decay won't happen here in any part.
– felix
Nov 20 '18 at 19:26