There's going to be a lot of code in this post, so I want to apologize for that right off the bat. Code that's not related to the problem and most function documentation has been removed, so feel free to ask for it if it becomes relevant.
I'm trying to make a print essentially acts as a "dispatch" to a println function, which has multiple overloads for different data types. In my code, everything is in a cu
namespace, hence why it exists in my question. Here's some code to help understand how this system works. My problem is specific to the cu::ptext
class when trying to print a std::vector<cu::ptext>
object.
FILE: println.h
// All needed includes
namespace cu
{
template<typename Dp>
concept DefaultPrintable = requires(std::ostream& os, Dp ln)
{
os << ln;
};
}
#ifdef _WIN32
namespace cu
{
template<typename Po>
concept PrettyTextObject = std::derived_from<Po, console::ptext> && requires(Po text)
{
text.display();
};
template<typename Ip>
concept IteratorPrintable = !DefaultPrintable<Ip> && requires(Ip container)
{
std::begin(container);
std::end(container);
requires DefaultPrintable<decltype(*std::begin(container))> ||
PrettyTextObject<decltype(*std::begin(container))>;
};
}
#endif
namespace cu
{
template<DefaultPrintable Dp>
void println(Dp line)
{
std::cout << line;
}
}
#ifdef _WIN32
namespace cu
{
template<PrettyTextObject Po>
void println(const Po& pretty_text)
{
pretty_text.display();
}
}
#endif
namespace cu
{
template<IteratorPrintable Ip>
/**
Allows iteratiable objects with well-formed std::begin() and std::end() to
be printed, with each element being printed on its own line. THIS FUNCTION
SHOULD NOT BE ACCESSED DIRECTLY. Instead, use cu::print() or cu::prompt().
@param container The container whose elements should be printed.
*/
void println(Ip container)
{
// Figure out size of container
size_t elements_ct = 0;
for (auto el = std::begin(container); el != std::end(container); el)
elements_ct ;
if (elements_ct > 0)
{
using ItTy = decltype(std::begin(container));
struct ContainerLoop { ItTy itr; size_t i; };
for (ContainerLoop cl{ std::begin(container), 0 }; cl.itr != std::end(container); cl.itr)
{
println(*cl.itr);
if (cl.i < elements_ct - 1)
std::cout << '\n';
}
}
}
}
namespace cu
{
template<typename Ty>
concept Printable = requires(Ty ln)
{
println(ln);
};
}
FILE: print.h
namespace cu
{
/**
Null case for print() which flushes the stream.
*/
inline void print()
{
std::cout << std::flush;
}
template<Printable Pt, Printable... Pts>
/**
Prints all arguments to console on their own line. All arguments must be
well-defined for cu::println([arg]).
@param line Current object to print to console
@param others All other objects to send through recursive function call
*/
void print(Pt line, Pts... others)
{
println(line);
std::cout << '\n';
print(std::forward<Pts>(others)...);
}
}
DRIVER CODE
cu::print("This is a test"); // <-- This line compiles fine
std::vector<std::string> stest{ "This", "is", "test" };
cu::print(stest); // <-- Also compiles fine
cu::print(cu::ptext("This is a test")); // <-- Compiles as well
std::vector<cu::ptext> ptest{ "This", "is", "test" };
cu::print(ptest); // <-- This line does not compile
To reproduce this error: https://godbolt.org/z/KroK4YehG
(Which has the following compiler output)
<source>:151:1: error: no matching function for call to 'print'
cu::print(ptest); // <-- This line does not compile
^~~~~~~~~
<source>:133:10: note: candidate template ignored: constraints not satisfied [with Pt = std::vector<cu::ptext>, Pts = <>]
void print(Pt line, Pts... others)
^
<source>:125:14: note: because 'std::vector<cu::ptext>' does not satisfy 'Printable'
template<Printable Pt, Printable... Pts>
^
<source>:111:9: note: because 'println(ln)' would be invalid: no matching function for call to 'println'
println(ln);
^
<source>:120:17: note: candidate function not viable: requires 0 arguments, but 1 was provided
inline void print()
^
1 error generated.
Compiler returned: 1
CodePudding user response:
The problem is here
template<typename Ip>
concept IteratorPrintable = !DefaultPrintable<Ip> && requires(Ip container) {
std::begin(container);
std::end(container);
requires DefaultPrintable<decltype(*std::begin(container))> ||
PrettyTextObject<decltype(*std::begin(container))>;
};
decltype(*std::begin(container))
gives you a reference type, which makes std::derived_from
in PrettyTextObject
fail.
The workaround is to use remove_reference
to remove the reference, or more simply, just use ranges::range_value_t
, which already constrains Ip
to be a range
.
template<typename Ip>
concept IteratorPrintable = !DefaultPrintable<Ip> &&
(DefaultPrintable<std::ranges::range_value_t<Ip>> ||
PrettyTextObject<std::ranges::range_value_t<Ip>>);