Discussion:
function to concatenate multiple string_views
(too old to reply)
Bonita Montero
2024-05-29 17:01:59 UTC
Permalink
I sometimes needed a function to concatenate an arbitrary number of
string_views to a string. The problem with that that variadic functions
only work with generic types. So I can't write sth. like:

#include <string_view>
#include <string>

using namespace std;

template<typename Allocator, typename Char, typename Traits>
auto sv_concat( basic_string_view<Char, Traits> svs ... )
{
basic_string<Char, Traits, Allocator> str;
str.reserve( (svs.length() + ...) );
((str += svs), ...);
return str;
}

So I did this genrically and I constrained the generic types to
actually be string_views. I made some header out of that which
looks like this:

#pragma once
#include <tuple>
#include <type_traits>
#include <concepts>
#include <string_view>

template<typename Allocator, typename ... Views>
requires (sizeof ...(Views) >= 1) && (std::same_as<Views,
std::basic_string_view<typename Views::value_type, typename
Views::traits_type>> && ...)
auto sv_concat( Views ... svs )
{
using namespace std;
using sv_type = tuple_element_t<0, tuple<Views ...>>;
using char_type = sv_type::value_type;
using traits_type = sv_type::traits_type;
basic_string<char_type, traits_type, Allocator> str;
str.reserve( (svs.length() + ...) );
((str += svs), ...);
return str;
}

The only problem with that is that all parameters must be string_views
and can't be sth. which is convertible to a string_view.
Bonita Montero
2024-05-29 17:35:17 UTC
Permalink
I made the code somewhat more convenient as the fist parameter needs to
be a string_view and the following parameters need to be convertible to
a string_view, i.e. you could supply simple char-pointers or strings:

#pragma once
#include <tuple>
#include <type_traits>
#include <concepts>
#include <string_view>
#include <array>

template<typename Allocator, typename Char, typename Traits, typename
... Viewables>
requires (sizeof ...(Viewables) >= 1) &&
(std::convertible_to<Viewables, std::basic_string_view<Char, Traits>> &&
...)
auto sv_concat( std::basic_string_view<Char, Traits> first, Viewables
&&... viewables )
{
using namespace std;
using sv_type = basic_string_view<Char, Traits>;
array<sv_type, sizeof ...(Viewables)> views = { sv_type( viewables ) ... };
basic_string<Char, Traits, Allocator> str;
auto adjust = [&]<size_t ... Indices>( index_sequence<Indices ...> )
{
str.reserve( (views[Indices].length() + ...) );
};
auto iseq = make_index_sequence<sizeof ...(Viewables)>();
adjust( iseq );
auto add = [&]<size_t ... Indices>( index_sequence<Indices ...> )
{
((str += views[Indices]), ...);
};
return str;
}
Bonita Montero
2024-05-29 18:10:50 UTC
Permalink
Now it's really convenient: You simply supply a string-type as the
first template-parameter and the value_type and traits_type is enforced
to match the corresponding types of the string_view

#pragma once
#include <type_traits>
#include <concepts>
#include <string_view>
#include <array>

template<typename String, typename Char, typename Traits, typename ...
Viewables>
requires (sizeof ...(Viewables) >= 1)
&& std::same_as<String, std::basic_string<typename String::value_type,
typename String::traits_type, typename String::allocator_type>>
&& std::same_as<Char, typename String::value_type>
&& std::same_as<Traits, typename String::traits_type>
&& (std::convertible_to<Viewables, std::basic_string_view<Char,
Traits>> && ...)
String sv_concat( std::basic_string_view<Char, Traits> first, Viewables
const &... viewables )
{
using namespace std;
using sv_type = basic_string_view<Char, Traits>;
array<sv_type, sizeof ...(Viewables)> views = { sv_type( viewables ) ... };
String str;
auto adjust = [&]<size_t ... Indices>( index_sequence<Indices ...> )
{
str.reserve( (views[Indices].length() + ...) );
};
auto iseq = make_index_sequence<sizeof ...(Viewables)>();
adjust( iseq );
auto add = [&]<size_t ... Indices>( index_sequence<Indices ...> )
{
((str += views[Indices]), ...);
};
return str;
}

This shows how to use the code:

#include <iostream>
#include "string_view.h"

using namespace std;

int main()
{
cout << sv_concat<string>( "hello "sv, "world" ) << endl;
}

Most of the code inside sv_concat is optimized away.
wij
2024-05-30 00:15:38 UTC
Permalink
Post by Bonita Montero
Now it's really convenient: You simply supply a string-type as the
first template-parameter and the value_type and traits_type is enforced
to match the corresponding types of the string_view
#pragma once
#include <type_traits>
#include <concepts>
#include <string_view>
#include <array>
template<typename String, typename Char, typename Traits, typename ...
Viewables>
requires (sizeof ...(Viewables) >= 1)
&& std::same_as<String, std::basic_string<typename String::value_type,
typename String::traits_type, typename String::allocator_type>>
&& std::same_as<Char, typename String::value_type>
&& std::same_as<Traits, typename String::traits_type>
&& (std::convertible_to<Viewables, std::basic_string_view<Char,
Traits>> && ...)
String sv_concat( std::basic_string_view<Char, Traits> first, Viewables
const &... viewables )
{
using namespace std;
using sv_type = basic_string_view<Char, Traits>;
array<sv_type, sizeof ...(Viewables)> views = { sv_type( viewables ) ... };
String str;
auto adjust = [&]<size_t ... Indices>( index_sequence<Indices ...> )
{
str.reserve( (views[Indices].length() + ...) );
};
auto iseq = make_index_sequence<sizeof ...(Viewables)>();
adjust( iseq );
auto add = [&]<size_t ... Indices>( index_sequence<Indices ...> )
{
((str += views[Indices]), ...);
};
return str;
}
#include <iostream>
#include "string_view.h"
using namespace std;
int main()
{
cout << sv_concat<string>( "hello "sv, "world" ) << endl;
}
Most of the code inside sv_concat is optimized away.
#include <Wy.stdio.h>

using namespace Wy;

int main() {
String str; str << "hello " << "world";
cout << str;
};

What is the purpose of long code of sv_concat<string> ?
String str; str << "hello " << "world";
cout << str;
};
Bonita Montero
2024-05-30 02:28:05 UTC
Permalink
Post by wij
Post by Bonita Montero
Now it's really convenient: You simply supply a string-type as the
first template-parameter and the value_type and traits_type is enforced
to match the corresponding types of the string_view
#pragma once
#include <type_traits>
#include <concepts>
#include <string_view>
#include <array>
template<typename String, typename Char, typename Traits, typename ...
Viewables>
requires (sizeof ...(Viewables) >= 1)
&& std::same_as<String, std::basic_string<typename String::value_type,
typename String::traits_type, typename String::allocator_type>>
&& std::same_as<Char, typename String::value_type>
&& std::same_as<Traits, typename String::traits_type>
&& (std::convertible_to<Viewables, std::basic_string_view<Char,
Traits>> && ...)
String sv_concat( std::basic_string_view<Char, Traits> first, Viewables
const &... viewables )
{
using namespace std;
using sv_type = basic_string_view<Char, Traits>;
array<sv_type, sizeof ...(Viewables)> views = { sv_type( viewables ) ... };
String str;
auto adjust = [&]<size_t ... Indices>( index_sequence<Indices ...> )
{
str.reserve( (views[Indices].length() + ...) );
};
auto iseq = make_index_sequence<sizeof ...(Viewables)>();
adjust( iseq );
auto add = [&]<size_t ... Indices>( index_sequence<Indices ...> )
{
((str += views[Indices]), ...);
};
return str;
}
#include <iostream>
#include "string_view.h"
using namespace std;
int main()
{
cout << sv_concat<string>( "hello "sv, "world" ) << endl;
}
Most of the code inside sv_concat is optimized away.
#include <Wy.stdio.h>
using namespace Wy;
int main() {
String str; str << "hello " << "world";
cout << str;
};
What is the purpose of long code of sv_concat<string> ?
String str; str << "hello " << "world";
cout << str;
};
With my code you can concatenate multiple objects which are
conveertible to a string_view as efficient as possible.
Bonita Montero
2024-05-30 08:49:36 UTC
Permalink
I forgot to add the primary non-generic string_view.
And I simplified the both index_sequence lambdas,
making them unnamed:

#pragma once
#include <type_traits>
#include <concepts>
#include <string_view>
#include <array>

template<typename String, typename Char, typename Traits, typename ...
Viewables>
requires (sizeof ...(Viewables) >= 1)
&& std::same_as<String, std::basic_string<typename String::value_type,
typename String::traits_type, typename String::allocator_type>>
&& std::same_as<Char, typename String::value_type>
&& std::same_as<Traits, typename String::traits_type>
&& (std::convertible_to<Viewables, std::basic_string_view<Char,
Traits>> && ...)
inline String sv_concat( std::basic_string_view<Char, Traits> first,
Viewables const &... viewables )
{
using namespace std;
using sv_type = basic_string_view<Char, Traits>;
array<sv_type, sizeof ...(Viewables)> views = { sv_type( viewables ) ... };
String str;
auto iseq = make_index_sequence<sizeof ...(Viewables)>();
[&]<size_t ... Indices>( index_sequence<Indices ...> )
{
str.reserve( first.length() + (views[Indices].length() + ...) );
}( iseq );
[&]<size_t ... Indices>( index_sequence<Indices ...> )
{
str += first;
((str += views[Indices]), ...);
}( iseq );
return str;
}
Bonita Montero
2024-05-30 14:43:43 UTC
Permalink
I've simplified the code further. Now the value_type and traits_type
isn't take from the first string_view parameter as a reference but
from the explicitly given String-type. Because of that every parameter
can be anything which is convertible to a basic_string_view according
to the string-"reflection".

template<typename String, typename ... Viewables>
requires (sizeof ...(Viewables) >= 1)
&& std::same_as<String, std::basic_string<typename String::value_type,
typename String::traits_type, typename String::allocator_type>>
&& (std::convertible_to<Viewables, std::basic_string_view<typename
String::value_type, typename String::traits_type>> && ...)
inline String sv_concat( Viewables const &... viewables )
{
using namespace std;
using sv_type = basic_string_view<typename String::value_type, typename
String::traits_type>;
array<sv_type, sizeof ...(Viewables)> views = { viewables ... };
String str;
auto iseq = make_index_sequence<sizeof ...(Viewables)>();
[&]<size_t ... Indices>( index_sequence<Indices ...> )
{
str.reserve( (views[Indices].length() + ...) );
}( iseq );
[&]<size_t ... Indices>( index_sequence<Indices ...> )
{
((str += views[Indices]), ...);
}( iseq );
return str;
}
Bonita Montero
2024-06-01 16:08:53 UTC
Permalink
Someone suggestet that allowing zero string_views would be possible also
with a if constexpr():

#pragma once
#include <type_traits>
#include <concepts>
#include <string_view>
#include <array>

template<typename String, typename ... Viewables>
requires std::same_as<String, std::basic_string<typename
String::value_type, typename String::traits_type, typename
String::allocator_type>>
&& (std::convertible_to<Viewables, std::basic_string_view<typename
String::value_type, typename String::traits_type>> && ...)
inline String sv_concat( Viewables &... viewables )
{
using namespace std;
String str;
constexpr size_t N = sizeof ...(Viewables);
if constexpr( N )
{
using sv_type = basic_string_view<typename String::value_type,
typename String::traits_type>;
array<sv_type, N> views = { viewables ... };
auto iseq = make_index_sequence<sizeof ...(Viewables)>();
[&]<size_t ... Indices>( index_sequence<Indices ...> )
{
str.reserve( (views[Indices].length() + ...) );
}( iseq );
[&]<size_t ... Indices>( index_sequence<Indices ...> )
{
((str += views[Indices]), ...);
}( iseq );
}
return str;
}

Bonita Montero
2024-05-30 03:53:01 UTC
Permalink
I forgot to add the static first string_view:

#pragma once
#include <type_traits>
#include <concepts>
#include <string_view>
#include <array>

template<typename String, typename Char, typename Traits, typename ...
Viewables>
requires (sizeof ...(Viewables) >= 1)
&& std::same_as<String, std::basic_string<typename String::value_type,
typename String::traits_type, typename String::allocator_type>>
&& std::same_as<Char, typename String::value_type>
&& std::same_as<Traits, typename String::traits_type>
&& (std::convertible_to<Viewables, std::basic_string_view<Char,
Traits>> && ...)
String sv_concat( std::basic_string_view<Char, Traits> first, Viewables
const &... viewables )
{
using namespace std;
using sv_type = basic_string_view<Char, Traits>;
array<sv_type, sizeof ...(Viewables)> views = { sv_type( viewables ) ... };
String str;
auto adjust = [&]<size_t ... Indices>( index_sequence<Indices ...> )
{
str.reserve( first.length() + (views[Indices].length() + ...) );
};
auto iseq = make_index_sequence<sizeof ...(Viewables)>();
adjust( iseq );
auto add = [&]<size_t ... Indices>( index_sequence<Indices ...> )
{
str += first;
((str += views[Indices]), ...);
};
return str;
}
Alf P. Steinbach
2024-05-30 22:17:56 UTC
Permalink
Post by Bonita Montero
I sometimes needed a function to concatenate an arbitrary number of
string_views to a string. The problem with that that variadic functions
[snip code] >
The only problem with that is that all parameters must be string_views
and can't be sth. which is convertible to a string_view.
A solution can go like this:

auto sv_concat_list(
in_<initializer_list<string_view>> views,
Buffer_<string> buffer = {string()}
) -> string
{
const int size = invoke( [&views]() -> int
{
int result = 0;
for( const string_view sv: views ) { result += intsize_of(
sv ); }
return result;
} );
string& result = buffer.o;
result.reserve( size );
for( const string_view sv: views ) { result.append( sv ); }
return move( result );
}

// Convenience interface for when caller does not supply a buffer:
template< class... Args >
auto sv_concat( in_<Args>... args )
-> string
{ return sv_concat_list( initializer_list<string_view>( args... ) ); }

This uses some stuff:

template< class Type > using in_ = const Type&;

template< class Collection >
auto intsize_of( in_<Collection> c ) { return static_cast<int>(
std::size( c ) ); }

template< class Type > struct Buffer_
{
Type&& o; Buffer_( Type&& _o ): o( move( _o ) ) {} //
Constructor to avoid MSVC ICE (!).
};

Disclaimer: I haven't tested this code. Perhaps you can supply the test
program(s) you used, and report if this passes?

- Alf
Alf P. Steinbach
2024-05-30 22:20:31 UTC
Permalink
Oh sorry I wrote round parens where there should be curly braces, like this:

// Convenience interface for when caller does not supply a buffer:
template< class... Args >
auto sv_concat( in_<Args>... args )
-> string
{ return sv_concat_list( initializer_list<string_view>{ args... } ); }


- Alf
Alf P. Steinbach
2024-05-31 00:19:44 UTC
Permalink
Post by Bonita Montero
I sometimes needed a function to concatenate an arbitrary number of
string_views to a string. The problem with that that variadic functions
[snip code] >
The only problem with that is that all parameters must be string_views
and can't be sth. which is convertible to a string_view.
    auto sv_concat_list(
        in_<initializer_list<string_view>>  views,
        Buffer_<string>                     buffer = {string()}
        ) -> string
    {
        const int size = invoke( [&views]() -> int
        {
            int result = 0;
            for( const string_view sv: views ) { result += intsize_of(
sv ); }
            return result;
        } );
        string& result = buffer.o;
Here should be a call to `result.clear()`. Which doesn't reduce capacity.
        result.reserve( size );
        for( const string_view sv: views ) { result.append( sv ); }
        return move( result );
    }
Bonita Montero
2024-05-31 09:27:58 UTC
Permalink
    auto sv_concat_list(
        in_<initializer_list<string_view>>  views,
        Buffer_<string>                     buffer = {string()}
        ) -> string
    {
        const int size = invoke( [&views]() -> int
        {
            int result = 0;
            for( const string_view sv: views ) { result += intsize_of(
sv ); }
            return result;
        } );
I don't see why it should make sense to use a function-object for that.
And if you do that you could simply call it directly by appending ()
without invoke. And below you iterate over the string_views without
having function-isolated code.
        string& result = buffer.o;
        result.reserve( size );
        for( const string_view sv: views ) { result.append( sv ); }
        return move( result );
    }
    template< class... Args >
    auto sv_concat( in_<Args>... args )
        -> string
    { return sv_concat_list( initializer_list<string_view>( args... ) ); }
I guess your solution somehat is slower since youre explicitly looping
over the initalizer list. And it's not generic so that it automatically
works with a wide-string or a string with different traits, different
allocator or whatever.
Loading...