Just use a template to print any container type containing a std::wstring
or std::wstring_view
(two arbitrary type limitations for the sake of demonstration; easily adjust or remove these limitations as you see fit)
I prefer to stick with code that is more universally-readable (C++ "concepts" are very advanced and not as widely understood). Why not just use this simple template?
template <typename T>
void print(const T& matrix) {
for (auto const& str : matrix) {
for (auto const ch : str) {
std::wcout << ch;
}
std::wcout << '\n';
}
}
As a bonus, add in this static_assert
to check types and ensure that only std::wstring
or std::wstring_view
string types are passed in, for example (modify or delete the static assert as you see fit, and according to your needs):
static_assert(std::is_same_v<decltype(str), const std::wstring&> ||
std::is_same_v<decltype(str), const std::wstring_view&>,
"Only strings of `std::wstring` or `std::wstring_view` are "
"allowed!");
Now you have this better version of the print()
function template:
template <typename T>
void print(const T& matrix) {
for (auto const& str : matrix) {
static_assert(std::is_same_v<decltype(str), const std::wstring&> ||
std::is_same_v<decltype(str), const std::wstring_view&>,
"Only strings of `std::wstring` or `std::wstring_view` are "
"allowed!");
for (auto const ch : str) {
std::wcout << ch;
}
std::wcout << '\n';
}
}
But, the 2nd usage of auto
isn't necessary and adds no value (it just obfuscates things), so let's remove it, and use this instead:
for (wchar_t const ch : str) {
The 1st usage of auto
is fine because it's required there since it could be multiple types.
(Note: if you actually do need to handle other types of chars here, ignore what I'm saying here and change wchar_t
back to auto
. That's up to you to decide.)
Now, we have this final version of the printf()
function template:
template <typename T>
void print(const T& matrix) {
for (auto const& str : matrix) {
static_assert(std::is_same_v<decltype(str), const std::wstring&> ||
std::is_same_v<decltype(str), const std::wstring_view&>,
"Only strings of `std::wstring` or `std::wstring_view` are "
"allowed!");
for (wchar_t const ch : str) {
std::wcout << ch;
}
std::wcout << '\n';
}
}
It appears your goal is to be able to print any container type containing wide-char text with your custom print()
function, no?
You seem to be calling this a "matrix", where the outer element in the container is a string, and the inner element of each string is a wide character (wchar).
If that's the case, the following template works just fine. I simply changed this:
void print(std::span<std::span<wchar_t>> matrix) {
to this:
template <typename T>
void print(const T& matrix) {
...and then I added:
- a
static_assert
relying on A) std::is_same_v<>
(same as std::is_same<>::value
) and B) the decltype()
specifier to ensure only std::wstring
or std::wstring_view
string types are passed in, and
- some more test prints in
main()
, including test prints for a std::vector<std::wstring>
and a std::vector<std::wstring_view>
, as well as for a linked list: std::list<std::wstring_view>
, and an unordered set (hash set): std::unordered_set<std::wstring>
.
Here is the entire code and print()
function template. Run this code online: https://godbolt.org/z/TabW43Yjf.
#include <iostream>
#include <list> // added for demo purposes to print a linked list in main()
// #include <span> // not needed
#include <string>
#include <type_traits> // added to check types and aid with static asserts
#include <unordered_set>
#include <vector>
template <typename T>
void print(const T& matrix) {
for (auto const& str : matrix) {
static_assert(std::is_same_v<decltype(str), const std::wstring&> ||
std::is_same_v<decltype(str), const std::wstring_view&>,
"Only strings of `std::wstring` or `std::wstring_view` are "
"allowed!");
for (wchar_t const ch : str) {
std::wcout << ch;
}
std::wcout << '\n';
}
}
int main() {
std::vector<std::wstring> vec1 = {L"Cool1", L"Cool2", L"Cool3"};
std::vector<std::wstring_view> vec2 = {L"Hey1", L"Hey2", L"Hey3"};
std::list<std::wstring_view> list1 = {L"You1", L"You2", L"You3"};
std::unordered_set<std::wstring> set1 = {L"There1", L"There2", L"There3"};
print(vec1);
print(vec2);
print(list1);
print(set1);
// Compile-time error due to the std::is_same_v<> usage in the static_assert
// above!
// std::vector<std::string> vec3 = {"hey", "you"};
// print(vec3);
}
Sample Output:
Cool1
Cool2
Cool3
Hey1
Hey2
Hey3
You1
You2
You3
There3
There2
There1
If you just want to print std::vector<std::wstring>
and std::vector<std::wstring_view>
types, here is a more-limited template (again, these are two arbitrary type limitations for the sake of demonstration; easily adjust or remove these limitations as you see fit):
Simply replace this in my template above:
template <typename T>
void print(const T& matrix) {
with this, to force it to only accept std::vector<>
container types (const T&
above changes to const std::vector<T>&
below, is all):
template <typename T>
void print(const std::vector<T>& matrix) {
Then, add a static_assert
to ensure the type inside the vector is either std::wstring
or std::wstring_view
, as desired.
Full code below. Run it online here: https://godbolt.org/z/qjhqq647M.
#include <iostream>
// #include <span> // not needed
#include <string>
#include <type_traits>
#include <vector>
template <typename T>
void print(const std::vector<T>& matrix) {
static_assert(std::is_same_v<T, std::wstring> ||
std::is_same_v<T, std::wstring_view>,
"Only vectors of `std::wstring` or `std::wstring_view` are allowed!");
for (auto const& str : matrix) {
for (wchar_t const ch : str) {
std::wcout << ch;
}
std::wcout << '\n';
}
}
int main() {
std::vector<std::wstring> vec1 = {L"Cool1", L"Cool2", L"Cool3"};
std::vector<std::wstring_view> vec2 = {L"Hey1", L"Hey2", L"Hey3"};
print(vec1);
print(vec2);
// Compile-time error due to the std::is_same_v<> usage in the static_assert
// above!
// std::vector<std::string> vec3 = {"hey", "you"};
// print(vec3);
}
Why a span of spans doesn't work:
std::span<T>
is essentially just a struct containing a pointer to a block of contiguous memory. Cppreference.com states (emphasis added):
The class template span describes an object that can refer to a contiguous sequence of objects with the first element of the sequence at position zero.
As I explain in my other answer on spans here (What is a "span" and when should I use one?), it might look like this:
template <typename T>
struct span
{
T * ptr_to_array; // pointer to a contiguous C-style array of data
// (which memory is NOT allocated or deallocated
// by the span)
std::size_t length; // number of elements in the array
// Plus a bunch of constructors and convenience accessor methods here
}
Not all C++ container types are stored in contiguous memory, however, such as linked lists (std::list
and std::forward_list
) so they cannot be placed into a span.
Generally-speaking, a span is a wrapper in C++ to wrap around C-style arrays, capturing in one variable a pointer to their contiguous block of memory, and in another variable, their length. This way, you can replace a function prototype with two input parameters like this:
void do_stuff(T *ptr_to_data, std::size_t num_elements) {}
// OR (the const form)
void do_stuff(const T *ptr_to_data, std::size_t num_elements) {}
with a prototype with one input parameter like this:
void do_stuff(std::span<T> data) {}
// OR (the const form)
void do_stuff(const std::span<T> data) {}
as @mcilloni says in his comment here.
References:
- Scratch work:
- https://godbolt.org/z/s99dnzj8z
- https://godbolt.org/z/33vzTM787
- [my answer] What is a "span" and when should I use one?
- https://www.learncpp.com/cpp-tutorial/an-introduction-to-stdstring_view/ - EXCELLENT read on what is a
std::string_view
, when and why to use it, and how. It also covers some of its nuances, limitations, and shortcomings.
- https://en.cppreference.com/w/cpp/container/span
- https://en.cppreference.com/w/cpp/types/is_same
- https://en.cppreference.com/w/cpp/header/type_traits
- *****[my answer--VERY USEFUL--I referenced it to remember how to statically check types at compile-time with
static_assert(std::is_same_v<decltype(var), some_type>, "some msg");
] Use static_assert to check types passed to macro