Reputation: 4451
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
.
Upvotes: 0
Views: 421
Reputation: 120079
You can try something like this:
template <int line, std::size_t N, basic_fixed_string<N> file>
class MyClassImpl {
public:
// copy from any instantiation
template <int line2, std::size_t N2, basic_fixed_string<N2> file2>
MyClassImpl(const MyClassImpl<line2, N2, file2>& other) { ... }
// also move, assignment etc
...
};
#define MyClass MyClassImpl<__LINE__, sizeof(__FILE__)-1, __FILE__>
You can take basic_fixed_string
from here or roll your own; also feel free to use your implementation of std::source_location
instead of the old-style macros if possible. The idea is to make each mention of MyClass
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
MyClass o3(getCopy1());
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
:
auto o3 = getCopy1(); // still has copy elision
so this is not a robust solution.
Upvotes: 1