I am trying to add instrumentation to a widely used template class in my product. I am currently on VS 2019 (16.10.4)
with /std:c++17
. The new feature of std::source_location
is a great addition for the kind of task I am interested in solving. While std::source_location
and std::experimental::source_location
are not available on my compiler, I built my own based on this answer. My goal is make changes to the class constructor/members in a special build and run tests. The changes to the class itself does not change its usage, so rest of the code remains same.
This all compiles and works great - mostly. Except, I run into the copy elision which almost beats the purpose of using std::source_location
. I'd like my source_location
to be the location at which the caller variable is defined instead of where the actual object is created.
This problem can be demonstrated on gcc -std=c++20
with and without -fno-elide-constructors
as well. See simplified version of my minimal reproducible godbolt sample.
MyClass:
class MyClass
{
private:
int m_a = 0;
std::source_location m_location;
public:
MyClass(std::source_location location = std::source_location::current())
: m_location(location)
{
}
MyClass(const MyClass &other, std::source_location location = std::source_location::current())
: m_a(other.m_a)
, m_location(location)
{
}
MyClass(MyClass &&other, std::source_location location = std::source_location::current())
: m_a(other.m_a)
, m_location(location)
{
}
};
Usage:
MyClass getCopy1()
{
MyClass ret;
return ret;
}
MyClass getCopy2()
{
return MyClass();
}
int main()
{
MyClass o1;
MyClass o2(o1);
MyClass o3(getCopy1());
MyClass o4(getCopy2());
std::cout << "o1: " << o1.getLocationInfo() << std::endl;
std::cout << "o2: " << o2.getLocationInfo() << std::endl;
std::cout << "o3: " << o3.getLocationInfo() << std::endl;
std::cout << "o4: " << o4.getLocationInfo() << std::endl;
return 0;
}
Actual Output:
o1: /app/example.cpp(56:13) int main()
o2: /app/example.cpp(57:18) int main()
o3: /app/example.cpp(46:12) MyClass getCopy1()
o4: /app/example.cpp(51:20) MyClass getCopy2()
Expected Output:
o1: /app/example.cpp(56:13) int main()
o2: /app/example.cpp(57:18) int main()
o3: /app/example.cpp(58:26) int main()
o4: /app/example.cpp(59:26) int main()
Workaround 1 (explicit argument):
MyClass o3(getCopy1(), std::source_location::current());
MyClass o4(getCopy2(), std::source_location::current());
Workaround 2 (std::move
):
MyClass o3(std::move(getCopy1()));
MyClass o4(std::move(getCopy2()));
While any of these workarounds above give me the results I desire, they are impractical. These will require me to change several instances of usage code and will beat the purpose of using something like std::source_location
. Any changes to the MyClass.h/cpp
that does not break its usage or any changes to the compiler flags is fair game. I intend to have a separate instrumented build that will not be used in production. My product currently builds on VS 2019 (16.10.4)
with /std:c++17
.
You can try something like this:
You can take
basic_fixed_string
from here or roll your own; also feel free to use your implementation ofstd::source_location
instead of the old-style macros if possible. The idea is to make each mention ofMyClass
a unique type. The template parameters are not used in the class definition, they could be anything, as long as they are unique.Now there's no longer copy elision when you do
because the types are not the same, so there's no copy to be elided.
Of course this doesn't work at all if you use
auto
:so this is not a robust solution.