c++ const ref vs template concept - c++

I'm studying the c++20 specifications for templates, in detail the concepts.
Currently for passing parameters to functions I use the const ref:
void writeMsg(const std::string& msg) {
std::cout << "msg = " << msg << "\n";
}
I have only now discovered that templates can also be used, and with concepts I can control the parameters passed. Example:
template<typename T>
concept is_string = std::is_convertible<T, std::string>::value;
template<is_string T>
void writeMsg2(T&& msg) {
std::cout << "msg =" << msg << "\n";
}
Personally I can't understand the benefits, maybe my case is wrong to get the benefits from the concepts? Do you have any suggestions or links to help me in this question?
Thanks

As you presented it: types which are convertible to std::string wouldn't need to be converted before the call to std::cout (potentially saving memory [de]allocations).
It's the only big advantage I can see looking at your code. However, flipping a bit or two:
template <class T>
concept String = std::is_convertible<T, std::string>::value;
void writeMsg2(String auto const& msg) {
std::cout << "msg = " << msg << "\n";
}
Live on Compiler Explorer

Related

Log anything i want using auto keyword

I programmed a log class that logs messages in color in the terminal. I want it to be able to log anything
I give it to log. I guess templates are the way to go. But can't i use auto as an argument type, then check if it is a string and if not call the tostring method of the object ?
Since c++20 you can use auto and overload for the other types you want to handle differently.
void log(auto test) {
std::cout << std::to_string(test) << std::endl;
}
void log(const std::string &str) {
std::cout << "str: " << str << std::endl;
}
int main()
{
log(std::string("test"));
log(10);
}
You could indeed use templates, then just add a template specialization for std::string that doesn't invoke std::to_string
#include <iostream>
#include <string>
template <typename T>
void ToLog(T t)
{
std::cout << std::to_string(t) << '\n';
}
template <>
void ToLog<std::string>(std::string str)
{
std::cout << str << '\n';
}
int main()
{
ToLog(5);
ToLog(12.0);
ToLog(std::string("hello"));
return 0;
}
Output
5
12.000000
hello
No, auto needs to determine the type of a variable in compile time, which can't be done until C++20. If you are using C++ standard, either you use templates, preprocessor macros (as some logging libraries do) or directly some to_string function before passing the argument.
But, as said, with C++20 it can be done, and behaves like a template.
You might find this question useful.

Using concepts for function overload resolution (instead of SFINAE)

Trying to say goodbye to SFINAE.
Is it possible to use concepts to distinguish between functions, so the compiler can match the correct function based on whether or not a sent parameter meets concept constraints?
For example, overloading these two:
// (a)
void doSomething(auto t) { /* */ }
// (b)
void doSomething(ConceptA auto t) { /* */ }
So when called the compiler would match the correct function per each call:
doSomething(param_doesnt_adhere_to_ConceptA); // calls (a)
doSomething(param_adheres_to_ConceptA); // calls (b)
Related question: Will Concepts replace SFINAE?
Yes concepts are designed for this purpose. If a sent parameter doesn't meet the required concept argument the function would not be considered in the overload resolution list, thus avoiding ambiguity.
Moreover, if a sent parameter meets several functions, the more specific one would be selected.
Simple example:
void print(auto t) {
std::cout << t << std::endl;
}
void print(std::integral auto i) {
std::cout << "integral: " << i << std::endl;
}
Above print functions are a valid overloading that can live together.
If we send a non integral type it will pick the first
If we send an integral type it will prefer the second
e.g., calling the functions:
print("hello"); // calls print(auto)
print(7); // calls print(std::integral auto)
No ambiguity -- the two functions can perfectly live together, side-by-side.
No need for any SFINAE code, such as enable_if -- it is applied already (hidden very nicely).
Picking between two concepts
The example above presents how the compiler prefers constrained type (std::integral auto) over an unconstrained type (just auto). But the rules also apply to two competing concepts. The compiler should pick the more specific one, if one is more specific. Of course if both concepts are met and none of them is more specific this will result with ambiguity.
Well, what makes a concept be more specific? if it is based on the other one1.
The generic concept - GenericTwople:
template<class P>
concept GenericTwople = requires(P p) {
requires std::tuple_size<P>::value == 2;
std::get<0>(p);
std::get<1>(p);
};
The more specific concept - Twople:
class Any;
template<class Me, class TestAgainst>
concept type_matches =
std::same_as<TestAgainst, Any> ||
std::same_as<Me, TestAgainst> ||
std::derived_from<Me, TestAgainst>;
template<class P, class First, class Second>
concept Twople =
GenericTwople<P> && // <= note this line
type_matches<std::tuple_element_t<0, P>, First> &&
type_matches<std::tuple_element_t<1, P>, Second>;
Note that Twople is required to meet GenericTwople requirements, thus it is more specific.
If you replace in our Twople the line:
GenericTwople<P> && // <= note this line
with the actual requirements that this line brings, Twople would still have the same requirements but it will no longer be more specific than GenericTwople. This, along with code reuse of course, is why we prefer to define Twople based on GenericTwople.
Now we can play with all sort of overloads:
void print(auto t) {
cout << t << endl;
}
void print(const GenericTwople auto& p) {
cout << "GenericTwople: " << std::get<0>(p) << ", " << std::get<1>(p) << endl;
}
void print(const Twople<int, int> auto& p) {
cout << "{int, int}: " << std::get<0>(p) << ", " << std::get<1>(p) << endl;
}
And call it with:
print(std::tuple{1, 2}); // goes to print(Twople<int, int>)
print(std::tuple{1, "two"}); // goes to print(GenericTwople)
print(std::pair{"three", 4}); // goes to print(GenericTwople)
print(std::array{5, 6}); // goes to print(Twople<int, int>)
print("hello"); // goes to print(auto)
We can go further, as the Twople concept presented above works also with polymorphism:
struct A{
virtual ~A() = default;
virtual std::ostream& print(std::ostream& out = std::cout) const {
return out << "A";
}
friend std::ostream& operator<<(std::ostream& out, const A& a) {
return a.print(out);
}
};
struct B: A{
std::ostream& print(std::ostream& out = std::cout) const override {
return out << "B";
}
};
add the following overload:
void print(const Twople<A, A> auto& p) {
cout << "{A, A}: " << std::get<0>(p) << ", " << std::get<1>(p) << endl;
}
and call it (while all the other overloads are still present) with:
print(std::pair{B{}, A{}}); // calls the specific print(Twople<A, A>)
Code: https://godbolt.org/z/3-O1Gz
Unfortunately C++20 doesn't allow concept specialization, otherwise we would go even further, with:
template<class P>
concept Twople<P, Any, Any> = GenericTwople<P>;
Which could add a nice possible answer to this SO question, however concept specialization is not allowed.
1 The actual rules for Partial Ordering of Constraints are more complicated, see: cppreference / C++20 spec.

What cost does bloated object file carry?

While working on an embedded project, I have encountered a function, which is called thousands of times in application's lifetime, often in loops, dozens of times per second. I wondered if I can reduce its cost and I found out, that most of its parameters are known during compilation.
Let me illustrate it with an example.
Original hpp/cpp files can be approximated like this:
original.hpp:
void example(bool arg1, bool arg2, const char* data);
original.cpp:
#include "ex1.hpp"
#include <iostream>
void example(bool arg1, bool arg2, const char* data)
{
if (arg1 && arg2)
{
std::cout << "Both true " << data << std::endl;
}
else if (!arg1 && arg2)
{
std::cout << "False and true " << data << std::endl;
}
else if (arg1 && !arg2)
{
std::cout << "True and false " << data << std::endl;
}
else
{
std::cout << "Both false " << data << std::endl;
}
}
Let's assume, that every single time the function is called, arg1 and arg2 are known during compilation. Argument data isn't, and for variety of reasons its processing cannot be put in header file.
However, all those if statements can be handled by the compiler with a little bit of template magic:
magic.hpp:
template<bool arg1, bool arg2>
void example(const char* data);
magic.cpp:
#include "ex1.hpp"
#include <iostream>
template<bool arg1, bool arg2>
struct Processor;
template<>
struct Processor<true, true>
{
static void process(const char* data)
{
std::cout << "Both true " << data << std::endl;
}
};
template<>
struct Processor<false, true>
{
static void process(const char* data)
{
std::cout << "False and true " << data << std::endl;
}
};
template<>
struct Processor<true, false>
{
static void process(const char* data)
{
std::cout << "True and false " << data << std::endl;
}
};
template<>
struct Processor<false, false>
{
static void process(const char* data)
{
std::cout << "Both false " << data << std::endl;
}
};
template<bool arg1, bool arg2>
void example(const char* data)
{
Processor<arg1, arg2>::process(data);
}
template void example<true, true>(const char*);
template void example<false, true>(const char*);
template void example<true, false>(const char*);
template void example<false, false>(const char*);
As you can see, even on this tiny example cpp file got significantly bigger compared to the original. But I did remove a few assembler instructions!
Now, in my real-life case things are a bit more complex, because instead of two bool arguments I have enums and structures. Long story short, all combinations give me about one thousand combinations, so I have that many instances of line template void example<something>(const char*);
Of course I do not generate them manually, but with macros, yet still cpp file gets humongous, compared to the original and object file is even worse.
All this in the name of removing several if and one switch statements.
My question is: is size the only problem with the template-magic approach? I wonder if there is some hidden cost with using so many versions of the same function. Did I really saved some resources, or just the opposite?
The problem with an increased binary size is almost never the storage of the file itself - the problem is that more code means a lower % of the program instructions are available in cache at any point, leading to cache misses. If you're calling the same instantiation in a tight loop, then having it do less work is great. But if you're constantly bouncing around between different template instantiations, then the cost of going to main memory to load instructions may be far higher than what you save by removing some instructions from inside the function.
This kind of thing can be VERY difficult to predict, though. The way to find the sweet spot in this (and any) type of optimization is to measure. It is also likely to change across platforms - especially in an embedded world.

using microsoft's krabsetw utility to parse structured types in ETW

So I've been examining for a while microsoft's krabsetw example on how to handle and parse ETW events. Their example showed a simple usage regarding top level's fields, But what happens when arrays and complex structures are involved?
Simple usage : multiple_providers_001.cpp :
krabs::parser parser(schema);
auto url = parser.parse<std::string>(L"URL");
auto request_headers = parser.parse<std::string>(L"RequestHeaders");
auto response_headers = parser.parse<std::string>(L"ResponseHeaders");
std::cout << "\tURL: " << url << std::endl;
std::cout << "\t\tRequest Headers: " << request_headers << std::endl;
std::cout << "\t\tResponse Headers: " << response_headers << std::endl;
Interface's deceleration:
template <typename T>
bool try_parse(const std::wstring &name, T &out);
/**
* <summary>
* Attempts to parse the given property by name and type. If the
* property does not exist, an exception is thrown.
* </summary>
*/
template <typename T>
T parse(const std::wstring &name);
template <typename Adapter>
auto view_of(const std::wstring &name, Adapter &adapter) -> collection_view<typename Adapter::const_iterator>;
So to summarize, In cases where i would be asked to provide the following's value: A\B\C\Struct\Array, such operation even plausible with their API? or should i convert everything into JSON and friends and extract it from there?

Is there a template debugger?

Templates can be programs in themselves.
Is there a template debugger so you can step thru the "execution" of the template?
This would basically have to be something that is done during compile/link/codegen - and is distinct from debugging the generated program.
Even in many "primitive" environments where you cannot use a debugger, you can usually do "printf debugging". Is even that possible with templates?
edit: Another way to think about this is something like the C preprocessor. It is often very useful to generate "preprocessed" source code - the output from the preprocessor that the compiler is actually compiling - this lets you see what effect your macros are having. A template equivalent would be great - have the compiler output the non-template source code which cooresponds to the templated input. The closest you can get I suppose is a C++ to C translator. (Doesn't the comeau compiler do this?)
You might want to look at this patch for clang that outputs template instantiations.
Another simple tool is the error messages your compiler produces for attempting to instantiate an undefined template.
template< typename > struct TD;
template< typename T >
void your_template_function( T & param )
{
// Both of these produce an error about "undefined type TD< T > with T = ..."
TD< T > test1;
TD< decltype( param ) > test2;
}
This is explained in Scott Meyers CPPCon talk, right after the ring-tailed lemur slide.
On the last years c++ conference there was a talk to that topic. Some of the informations you can find here:
http://gsd.web.elte.hu/contents/articles/gpce06.pdf
and
http://patakino.web.elte.hu/ECOOP_Templight_Poster.pdf
I have no idea how functional that stuff now is, but it was a very interesting startpoint.
I personal wrote me some helper classes which are able to print me the given types like printf debugging for standard code. If the compilation fails it often gives a good error message while calling the DebugPrinter and if the program compiles but the result is really stupid because the type expansion is not what I expect the DebugPrinter helps me a lot!
template< typename T>
int DebugPrintArgs( T arg )
{
std::cout << arg << ", ";
return 0;
}
template <typename Head, typename ... T>
class DebugPrinter: public DebugPrinter<T...>
{
public:
DebugPrinter()
{
std::cout << "--------------------------" << std::endl;
std::cout << __PRETTY_FUNCTION__ << std::endl;
std::cout << "--------------------------" << std::endl;
}
template< typename ...Y>
DebugPrinter( Y ... rest )
{
std::cout << "Construction of: " << __PRETTY_FUNCTION__ << " Values: " ;
ExpandWithConstructor{DebugPrintArgs( rest)...};
std::cout << std::endl;
}
};
template <typename Head>
class DebugPrinter< Head >
{
public:
DebugPrinter()
{
std::cout << "--------------------------" << std::endl;
std::cout << __PRETTY_FUNCTION__ << std::endl;
std::cout << "--------------------------" << std::endl;
}
};