I just discovered that the problem with the 3rd lambda was that I was not correctly comparing the elements correctly for equivalence and lexicographical comparison. The correct approach is outlined in bullet 3) for tuple comparison where it indicates that I should use the following approach.
3) (bool)(std::get<0>(lhs) < std::get<0>(rhs)) || (!(bool)(std::get<0>(rhs) < std::get<0>(lhs)) && lhstail < rhstail), where lhstail is lhs without its first element, and rhstail is rhs without its first element. For two empty tuples, returns false.
The fixed up lambda comparator for sorting firsty sorts according to the filename() temporary and then uses the efficient std::tie for the other elements in the tuple
std::cout << "Order[mPath.filename(), elem1, intVal]" << std::endl;
std::cout << "======================================" << std::endl;
std::sort(aStructs.begin(), aStructs.end(),
[](const StructA& lhs, const StructA& rhs){
// attempt at efficiency - but not quite right
// AHA, I think I figured it out - see tuple operator_cmp
// return value documentation which states
// (bool)(std::get<0>(lhs) < std::get<0>(rhs)) ||
// (!(bool)(std::get<0>(rhs) < std::get<0>(lhs)) &&
// lhstail < rhstail), where lhstail is lhs without
// its first element, and rhstail is rhs without its
// first element. For two empty tuples, returns false.
// --------------------------------------------------------
// edit thanks to @Praetorian for suggesting the following:
// --------------------------------------------------------
auto f1 = lhs.mPath.filename(); auto f2 = rhs.mPath.filename();
return std::tie(f1, lhs.elem1, lhs.intVal) < std::tie(f2, rhs.elem1, rhs.intVal);
});
Doing so made the first set of results identical to the 3rd - not incredibly efficient for the filename() temporary but at least I don't take the std::make_tuple hit for all elements in my struct. The updated live example is here