13

I am new to C++17 and to std::string_view. I learned that they are not null terminated and must be handled with care.

Is this the right way to printf() one?

#include<string_view>
#include<cstdio>

int main()
{
    std::string_view sv{"Hallo!"};
    printf("=%*s=\n", static_cast<int>(sv.length()), sv.data());
    return 0;
}

(or use it with any other printf-style function?)

Eugene Sh.
  • 17,802
  • 8
  • 40
  • 61
kuga
  • 1,483
  • 1
  • 17
  • 38
  • 8
    Why you want to use `printf` in c++ at all? – Marek R Jun 10 '22 at 14:00
  • 4
    *I learned that they are not null terminated* That's not entirely correct. A `string_view` can be non-null terminated, but only if you create it as such. `"Hallo"` has a null terminator, so `sv` will as well. – NathanOliver Jun 10 '22 at 14:01
  • 3
    Is this what you are asking? [Using printf with a non-null terminated string](https://stackoverflow.com/questions/3767284/using-printf-with-a-non-null-terminated-string) – Drew Dormann Jun 10 '22 at 14:03
  • I believe it should be `"=%.*s=\n"`... Just checked, that's correct. – john Jun 10 '22 at 14:05
  • @DrewDormann No, but this is clearly answerering the same question. Did not think about searching for this... – kuga Jun 10 '22 at 14:19
  • I don't get the downvote, although using `char str[] = {'H', 'e', 'l', 'l', 'o'}; std::string_view sv(str, 5);` in the question would have been better. Got my upvote anyway. – Ted Lyngmo Jun 10 '22 at 14:22
  • @MarekR I change existing code, which excessively uses printf(). I want to keep the style and not change to much of it. My code above is obviously just a shortended example. – kuga Jun 10 '22 at 14:26
  • 4
    Consider use `fmt` library it should easy to transform code which uses `printf`. – Marek R Jun 10 '22 at 14:30
  • @kuga Did you manage to get it to work? You said that both answers are _"clearly wrong"_ but they are both correct. See [`std::printf`](https://en.cppreference.com/w/cpp/io/c/fprintf) – Ted Lyngmo Jun 10 '22 at 15:53
  • @TedLyngmo Yes `%.*s` works. I meant: Without the dot, its clearly wrong. – kuga Jun 13 '22 at 08:07
  • @kuga Oh, then I misunderstood you and thought that since you didn't accept any answer, you didn't think they were correct. – Ted Lyngmo Jun 13 '22 at 08:13

3 Answers3

15

This is strange requirement, but it is possible:

std::string_view s{"Hallo this is longer then needed!"};
auto sub = s.substr(0, 5);
printf("=%.*s=\n", static_cast<int>(sub.length()), sub.data());

https://godbolt.org/z/nbeMWo1G1

As you can see you were close to solution.

Yun
  • 3,056
  • 6
  • 9
  • 28
Marek R
  • 32,568
  • 6
  • 55
  • 140
  • casting to an `int` can lead to UB. Does `printf` support passing a `size_t` for the length of the string? – NathanOliver Jun 10 '22 at 14:11
  • @NathanOliver No, only `int` is supported. – Ted Lyngmo Jun 10 '22 at 14:14
  • @eeroroka has the same answer, but I just say it here: The dot (`.`) is the __precision__, without its the __minimum field__ length which is clearly wrong. – kuga Jun 10 '22 at 14:18
  • btw: The requirement is to not change the existing code, which excessively uses printf(), to much and keep the style ;) – kuga Jun 10 '22 at 14:21
  • 1
    @kuga No, it's not wrong. The `*` is what receives the precision. So, `'.5s', sv.data()` or `'.*s', (int)sv.size(), sv.data()` does the same thing (as long as `size()` doesn't overflow the `int`). – Ted Lyngmo Jun 10 '22 at 14:24
  • 1
    `printf` requires `int` value there so cast is needed. `Star` parameter doesn't have any modifiers to change expected type. It is possible to implement safe casting to `int`, but IMO it would be overkill. – Marek R Jun 10 '22 at 14:25
  • The [overkill version](https://godbolt.org/z/Tz9eWY7ME) :-) – Ted Lyngmo Jun 10 '22 at 14:33
  • 1
    I was more thinking to implement template which could be called `clip_cast`. – Marek R Jun 10 '22 at 14:35
  • @MarekR `clip_cast` got a nice ring to it! – Ted Lyngmo Jun 10 '22 at 14:36
10

You can use:

assert(sv.length() <= INT_MAX);
std::printf(
    "%.*s",
    static_cast<int>(sv.length()),
    sv.data());
eerorika
  • 232,697
  • 12
  • 197
  • 326
-2

The thing to remember about string_view is that it will never modify the underlying character array. So, if you pass a C-string to the string_view constructor, the sv.data() method will always return the same C-string.

So, this specific case will always work:

#include <string_view>
#include <cstdio>

int main() {
    std::string_view sv {"Hallo!"};
    printf("%s\n", sv.data());
}
Bill Weinman
  • 2,036
  • 2
  • 17
  • 12
  • 6
    I'd warn against doing this in the general case, since not all views are null-terminated. – HolyBlackCat Jun 11 '22 at 05:12
  • 1
    @holyBlackCat – i was very specific that it's only for C-string constructed `string_view` objects. – Bill Weinman Jun 12 '22 at 17:52
  • not sure why this is "-2" since in the above requested case the string_view is null-term. see ctor #4 in string_view in cppref. Yes, it's a view that is not null term, but I'd change the original question to be more general. – Kobi Aug 03 '22 at 17:20
  • Access to basic_string_view::operator[](size()) (the nul terminator in this case) has undefined behaviour. You can use this trick because it works, but you should feel dirty every time, I do :-( – Sandy May 03 '23 at 00:33