Discussion:
Overloading function for string literal with size
(too old to reply)
Marcel Mueller
2024-05-17 15:25:24 UTC
Permalink
Assuming I have a function

void assign(const char* str);
void assign(const char* str, size_t len);

If this is called with a string literal, always the string length has to
be examined at runtime.

With

template<size_t N>
void assign(const char (&str)[N]);

I can capture the string length (including \0) at compile time.
But this overload is never used because the first overload still fits to
the literal due to the implicit conversion from array to pointer.

How to force the last overload with fixed size char arrays?


Marcel
Bonita Montero
2024-05-17 15:45:15 UTC
Permalink
Post by Marcel Mueller
Assuming I have a function
void assign(const char* str);
void assign(const char* str, size_t len);
If this is called with a string literal, always the string length has to
be examined at runtime.
With
template<size_t N>
void assign(const char (&str)[N]);
I can capture the string length (including \0) at compile time.
But this overload is never used because the first overload still fits to
the literal due to the implicit conversion from array to pointer.
How to force the last overload with fixed size char arrays?
Marcel
Take a string_view as an argument. With a char-literal
counting the characters is constexpr'd at compile-time.
Bonita Montero
2024-05-17 16:56:56 UTC
Permalink
Post by Bonita Montero
Post by Marcel Mueller
Assuming I have a function
void assign(const char* str);
void assign(const char* str, size_t len);
If this is called with a string literal, always the string length has
to be examined at runtime.
With
template<size_t N>
void assign(const char (&str)[N]);
I can capture the string length (including \0) at compile time.
But this overload is never used because the first overload still fits
to the literal due to the implicit conversion from array to pointer.
How to force the last overload with fixed size char arrays?
Marcel
Take a string_view as an argument. With a char-literal
counting the characters is constexpr'd at compile-time.
I don't believe it: MSVC is the only C++17-capable compiler that
actually constexpr-s the string counting:

#include <iostream>
#include <string_view>

using namespace std;

#if defined(_MSC_VER)
__declspec(noinline)
#elif defined(__GNUC__) || defined(__clang__)
__attribute__((noinline))
#endif
void svFn( string_view const &sv )
{
cout << sv << endl;
}

int main()
{
svFn( "hello world" );
}


Here's the string string_view creation:

lea rax, OFFSET FLAT:$SG37362
mov QWORD PTR $T1[rsp+8], 11
Paavo Helde
2024-05-17 17:56:40 UTC
Permalink
Post by Bonita Montero
void svFn( string_view const &sv )
In general, string_view should be passed by value, not by reference, as
it is small enough. When I started to use string_view I did some
research and figured out pass-by-value is more appropriate.
Bonita Montero
2024-05-17 18:01:11 UTC
Permalink
Post by Paavo Helde
Post by Bonita Montero
void svFn( string_view const &sv )
In general, string_view should be passed by value, not by reference, as
it is small enough. When I started to use string_view I did some
research and figured out pass-by-value is more appropriate.
If you pass a temporary it's on the stack anyway. ;-)
Bonita Montero
2024-05-20 14:13:43 UTC
Permalink
    lea    rax, OFFSET FLAT:$SG37362
    mov    QWORD PTR $T1[rsp+8], 11
This makes it work with g++ and clang++:

#include <iostream>
#include <string_view>

using namespace std;

constexpr string_view sv_count( char const *str )
{
char const *begin = str;
for( ; *str; ++str );
return string_view( begin, str );
}
#if defined(_MSC_VER)
__declspec(noinline)
#elif defined(__GNUC__) || defined(__clang__)
__attribute__((noinline))
#endif
void svFn( string_view const &sv )
{
cout << sv << endl;
}

int main()
{
svFn( sv_count( "hello world" ) );
}
Marcel Mueller
2024-05-18 06:01:01 UTC
Permalink
Post by Bonita Montero
Take a string_view as an argument. With a char-literal
counting the characters is constexpr'd at compile-time.
I should have mentioned C++11, sorry.

But I am in doubt that this would help.
When an overload with const char* exists why should the compiler prefer
a string_view as proxy class?


Marcel
Bonita Montero
2024-05-19 13:24:49 UTC
Permalink
Post by Marcel Mueller
Post by Bonita Montero
Take a string_view as an argument. With a char-literal
counting the characters is constexpr'd at compile-time.
I should have mentioned C++11, sorry.
But I am in doubt that this would help.
When an overload with const char* exists why should the compiler prefer
a string_view as proxy class?
I think with a string_view a char-poiner isn't necessary.
Otherwise the overload without conversion would be preferred.
Marcel Mueller
2024-05-22 07:17:42 UTC
Permalink
Post by Bonita Montero
Post by Marcel Mueller
But I am in doubt that this would help.
When an overload with const char* exists why should the compiler
prefer a string_view as proxy class?
I think with a string_view a char-poiner isn't necessary.
Otherwise the overload without conversion would be preferred.
I wonder how string_view manages to omit the call to strlen.
There seem to be some magic which also removes the trailing 0 that all
string literals have.

However, although I cannot use string_view because I could at most
upgrade to C++14 this got me into the right direction.
Introducing a proxy class reduces the overload resolution priority:

struct cstring
{ const char* Str;
cstring(const char* str) : Str(str) {}
};

Now overloads with cstring and const char (&)[N] may coexist and the
latter is preferred for string literals. :-)


Marcel
Bonita Montero
2024-05-22 09:55:39 UTC
Permalink
Post by Marcel Mueller
I wonder how string_view manages to omit the call to strlen.
It couldn't use since the char-type of basic_string_view can
be any trivial type and strlen only takes chars.
Post by Marcel Mueller
There seem to be some magic which also removes the trailing 0 that all
string literals have.
It's the condition inside the constexpr'd loop which I've shown
as a surrogate vor the missing constexpr'ness of libstdc++ and
lib++; it doesn't go further than the zero-terminator.
Post by Marcel Mueller
However, although I cannot use string_view because I could at most
upgrade to C++14 this got me into the right direction.
struct cstring
{       const char* Str;
        cstring(const char* str) : Str(str) {}
};
I wish there could be functional default-arguments that take one
of the primary function's parameters. Sth. like this would be nice:

auto count = []( char const *str ) -> size_t
{
char const *begin = str;
for( ; *str; ++str );
return str - begin;
};

void fn( char const *str, size_t n = count( str ) );

But referencing a parameter inside a default-argument isn't possible.
Marcel Mueller
2024-05-22 10:59:09 UTC
Permalink
Post by Bonita Montero
I wish there could be functional default-arguments that take one
auto count = []( char const *str ) -> size_t
{
    char const *begin = str;
    for( ; *str; ++str );
    return str - begin;
};
void fn( char const *str, size_t n = count( str ) );
But referencing a parameter inside a default-argument isn't possible.
This is normally a job for function overloading.
The benefit is that no longer every caller needs to implement the code.


Marcel
Bonita Montero
2024-05-22 11:52:12 UTC
Permalink
Post by Marcel Mueller
Post by Bonita Montero
I wish there could be functional default-arguments that take one
auto count = []( char const *str ) -> size_t
{
     char const *begin = str;
     for( ; *str; ++str );
     return str - begin;
};
void fn( char const *str, size_t n = count( str ) );
But referencing a parameter inside a default-argument isn't possible.
This is normally a job for function overloading.
The benefit is that no longer every caller needs to implement the code.
As you've seen the char-pointer catches everyhting which can be
converted from a char pointer.
Post by Marcel Mueller
Marcel
Markus Schaaf
2024-05-21 11:20:21 UTC
Permalink
Post by Marcel Mueller
Assuming I have a function
void assign(const char* str);
void assign(const char* str, size_t len);
If this is called with a string literal, always the string length has to
be examined at runtime.
With
template<size_t N>
void assign(const char (&str)[N]);
I can capture the string length (including \0) at compile time.
But this overload is never used because the first overload still fits to
the literal due to the implicit conversion from array to pointer.
How to force the last overload with fixed size char arrays?
As far as I know, it is impossible. If you think about it, you
may not want such an overload anyway. Use string_view or a custom
class like it, and avoid plain (char const *). I have used a
special class for string literals for a long time, before
string_view and constexpr were invented.

MfG
Marcel Mueller
2024-05-22 07:33:11 UTC
Permalink
Post by Marcel Mueller
How to force the last overload with fixed size char arrays?
As far as I know, it is impossible. If you think about it, you may not
want such an overload anyway. Use string_view or a custom class like it,
and avoid plain (char const *).
In fact I have done this now - see my answer to Bonita.

There is only one drawback. The additional conversion prevents some
other assignments that itself used some kind of conversion like
(operator const char*).


From my point of view the decay from an array to a pointer type is pure
pain that should never had made it into C++. But changing this would
break zillions of existing code. :-(

Maybe a function parameter should be declarable as "explicit" to forbid
such decay:
type& operator=(explicit const char* str);
int foo(int x, explicit int y); // does not bind long to y

Just an idea.

Something similar has been introduced with the option to explicitly
delete some unwanted overloads. But this does not cover this use case
because the decay to a pointer takes precedence over template overloads.


Marcel
Markus Schaaf
2024-05-21 11:54:20 UTC
Permalink
Post by Marcel Mueller
Assuming I have a function
void assign(const char* str);
void assign(const char* str, size_t len);
As another suggestion: If you like your interfaces lean and
clean, then the second overload is the only one really needed.
And if you prefer a more C-ish style, using plain pointer and
separate length (instead of something like string_view), then
C-ish convenience solutions are a good fit:

#define STR_LIT( s ) s, (sizeof s - 1)
assign( STR_LIT( "bla" ))

BR
Bonita Montero
2024-05-21 13:26:52 UTC
Permalink
Post by Marcel Mueller
Assuming I have a function
void assign(const char* str);
void assign(const char* str, size_t len);
As another suggestion: If you like your interfaces lean and clean, then
the second overload is the only one really needed. And if you prefer a
more C-ish style, using plain pointer and separate length (instead of
something like string_view), then C-ish convenience solutions are a good
#define STR_LIT( s )  s, (sizeof s - 1)
assign( STR_LIT( "bla" ))
BR
https://en.cppreference.com/w/cpp/iterator/size - 1
Alf P. Steinbach
2024-05-24 13:26:14 UTC
Permalink
Post by Marcel Mueller
Assuming I have a function
void assign(const char* str);
void assign(const char* str, size_t len);
If this is called with a string literal, always the string length has to
be examined at runtime.
With
template<size_t N>
void assign(const char (&str)[N]);
I can capture the string length (including \0) at compile time.
But this overload is never used because the first overload still fits to
the literal due to the implicit conversion from array to pointer.
How to force the last overload with fixed size char arrays?
Three possibilities, if compile time O(1) size is what you're after:

* string_view, if C++17 and OK to ditch the zero-termination guarantee.
* Reducing the overload priority, as you point out in-thread.
* Just template it.

A hopefully C++11-compatible example of "just template it":

-----------------------------------------------------------------------------
#include <iostream>

namespace app {
using std::cout, std::endl; // <iostream>

namespace impl {
template< class Param > struct Of_assign_;
template<> struct Of_assign_< const char* const > {
static void f( const char* s ) { cout << "Pointer: " << s
<< endl; }
};
template< size_t size > struct Of_assign_< const char[size] > {
static void f( const char (&s)[size] ) { cout << "Array: "
<< s << endl; }
};
} // namespace impl

template< class Param >
void assign( const Param& s ) { impl::Of_assign_<const Param>::f( s
); }

void run()
{
assign( "Should be array." );
assign( +"Should be pointer" );
}
} // namespace app

auto main() -> int { app::run(); }
-----------------------------------------------------------------------------

Can be made more readable by replacing the direct implementation class
with a traits class that e.g. provides unique types for pointer and
array, and using those types for ordinary overload resolution.

- Alf
Marcel Mueller
2024-05-25 05:14:48 UTC
Permalink
Post by Alf P. Steinbach
Post by Marcel Mueller
How to force the last overload with fixed size char arrays?
* string_view, if C++17 and OK to ditch the zero-termination guarantee.
* Reducing the overload priority, as you point out in-thread.
* Just template it.
Interesting approach. You basically telling me that the partial
specialization rules differ and prefer the array implementation in doubt.


Marcel

Loading...