Function consolidation

I'm severely suffering from the 'morning after the night before' syndrome and have even trouble using a keyboard! I knew it was a mistake having it during the week. Never again. If I type nonsense please have some patience...

I have this code that seems to do as expected for the return type:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#include <string>
#include <string_view>
#include <iostream>

auto test(std::string_view) {
	std::string_view output { "sv" };

	std::cout << "Process sv\n";

	return output;
}

auto test(const char*) {
	std::string output { "s" };

	std::cout << "Process char*\n";

	return output;
}

auto test(std::string&) {
	std::string_view output { "sv" };

	std::cout << "Process s&\n";

	return output;
}

int main() {
	std::string s { "foobar" };
	std::string_view sv { "qwerty" };

	std::cout << test("qwerty") << '\n';    // Should return type std::string
	std::cout << test(s) << '\n';           // Should return type std::string_view
	std::cout << test(sv) << '\n';          // Should return type std::string_view

}


The processing undertaken within each test function is the same (not shown for brevity and not relevant). What I'd like is these 3 to be combined in one function with the return type std::string if the func param is a temp (rvalue) otherwise type std::string_view. My brain currently refuses to process this...

Thanks.
A string_view doesn't hold data, it's just a pointer/size pair. So those returns are referencing reused memory.
Yes - for when the arg is an lvalue the return string_view is valid as it's referencing into the passed arg. If the passed is a temp/rvalue then that's why return a string.
> when the arg is an lvalue the return string_view is valid as it's referencing into the passed arg.

Not necessarily; an lvalue reference (to const) may be bound to a temporary object.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <iostream>
#include <string>
#include <string_view>

// if a temporary is bound to the reference parameter str, that temporary object
// exists only until the end of the full expression containing the call to this this function
std::string_view foo( const std::string& str )
{
    return str ; 
}

int main()
{
    const std::string str = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" ;
    
    const auto a = foo(str) ; // this is fine
    std::cout << a.substr(2) << '\n' ;

    const auto b = foo( str.substr(2) ) ; // the temporary object (substring) is destroyed at the end of this full expression
    std::cout << b << '\n' ; // *** UB *** (the string_view outlives the pointed-to character array)
}

http://coliru.stacked-crooked.com/a/ce64dfeb03f81cd0
Yes - so for L19 foo() should return a std::string and not std::string_view as the passed arg is a temp. For an lvalue arg (or an arg referring to an lvalue) return std::string_view otherwise return std::string.

Updated main() test code:

1
2
3
4
5
6
7
8
9
int main() {
	std::string s { "foobar" };
	std::string_view sv { "qwerty" };

	std::cout << test("qwerty") << '\n';    // Returns std::string OK
	std::cout << test(s) << '\n';           // Returns std::string_view OK
	std::cout << test(sv) << '\n';          // Returns std::string_view OK
	std::cout << test(s.substr(2)) << '\n'; // Should return type std::string - ERROR - RETURNS string_view
}


L8 is in error. The type returned should be std::string and NOT std::string_view
OK. I've now had 3 pots of black coffee and can just about walk in a straight line slowly...

I've added a 4th overload which seems to give the expected output:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#include <string>
#include <string_view>
#include <iostream>

auto test(std::string_view) {
	std::string_view output { "sv" };

	std::cout << "Process sv\n";

	return output;
}


auto test(const char*) {
	std::string output { "s" };

	std::cout << "Process char*\n";

	return output;
}

auto test(std::string&&) {
	std::string output { "s" };

	std::cout << "Process s\n";

	return output;
}

auto test(std::string&) {
	std::string_view output { "sv" };

	std::cout << "Process s&\n";

	return output;
}

int main() {
	std::string s { "foobar" };
	std::string_view sv { "qwerty" };

	std::cout << test("qwerty") << '\n';    // Returns std::string OK
	std::cout << test(s) << '\n';           // Returns std::string_view OK
	std::cout << test(sv) << '\n';          // Returns std::string_view OK
	std::cout << test(s.substr(2)) << '\n'; // Returns std::string OK
}


Is there any way any (or preferable all) of these 4 test() functions can be combined into 1 function (using templates or anything)?
Last edited on
OK. With my still-befudled brain, this seems to do what I want:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#include <string>
#include <string_view>
#include <iostream>
#include <typeinfo>

template<typename T>
	requires std::same_as<T, std::string> || std::same_as<T, std::string_view>
T test1(std::string_view) {
	T output { typeid(T).name() };

	return output;
}

auto test(std::string_view s) {
	return test1<std::string_view>(s);
}

auto test(const char* s) {
	return test1<std::string>(s);
}

auto test(std::string&& s) {
	return test1<std::string>(s);
}

auto test(std::string& s) {
	return test1<std::string_view>(s);
}

int main() {
	std::string s { "foobar" };
	std::string_view sv { "qwerty" };

	std::cout << test("qwerty") << '\n';    // Returns std::string OK
	std::cout << test(s) << '\n';           // Returns std::string_view OK
	std::cout << test(sv) << '\n';          // Returns std::string_view OK
	std::cout << test(s.substr(2)) << '\n'; // Returns std::string OK
}



class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> >
class std::basic_string_view<char,struct std::char_traits<char> >
class std::basic_string_view<char,struct std::char_traits<char> >
class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> >


For C++20/23 is there a 'better' way of doing this without all the overloading?
Two overloads:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include <iostream>
#include <string>
#include <string_view>
#include <type_traits>

std::string test( std::string_view str ) // overload 1
{
    return { str.begin(), str.end() } ;
}

std::string_view test( std::string& str ) // overload 2
{
    return str ;
}

int main()
{
    std::string str = "abcdefgh" ;
    const  std::string const_str{ str.rbegin(), str.rend() } ;

    using namespace std::literals ;

    std::cout << typeid( test(str) ).name() << '\n' // string_view (modifiable lvalue of type std::string, overload 2)

              << typeid( test( const_str ) ).name() << '\n' // string (lvalue of type const std::string, overload 1)
              << typeid( test( str.substr(2) ) ).name() << '\n' // string (prvalue of type std::string, overload 1)
              << typeid( test( "querty" ) ).name() << '\n' // string (const char*, overload 1)
              << typeid( test( "querty"sv ) ).name() << '\n' // string (string_view, overload 1)
              << typeid( test( "querty"s ) ).name() << '\n' // string (prvalue of type std::string, overload 1)
              << typeid( test( str.c_str() ) ).name() << '\n' ; // string (const char*, overload 1)
}

http://coliru.stacked-crooked.com/a/6adeb072b11f2e27
Topic archived. No new replies allowed.