Tuesday, April 18, 2023

Digest for comp.lang.c++@googlegroups.com - 7 updates in 3 topics

Malcolm McLean <malcolm.arthur.mclean@gmail.com>: Apr 18 12:10AM -0700

On Monday, 17 April 2023 at 14:02:40 UTC+1, David Brown wrote:
> auto x = xmin(values ...);
> return t < x ? t : x;
> }
 
Don't you need a termination condition here?
Real question, I'd be surprised if you made such a simple error.
But the code doesn't make any sense to someone not familiar
with the intricacies?
Muttley@dastardlyhq.com: Apr 18 08:16AM

On Mon, 17 Apr 2023 19:54:06 +0200
>> person who wrote it.
 
>Regex-Matching is straightforward and doesn't need
>much template aerobatics inside the function.
 
Face <- palm.
 
Never mind, you Just Don't Get It.
Bonita Montero <Bonita.Montero@gmail.com>: Apr 18 01:31PM +0200


>> Regex-Matching is straightforward and doesn't need
>> much template aerobatics inside the function.
 
> Face <- palm.
 
You don't need to facepalm.
The complexity of regex-matching is different than what I've shown.
 
Bonita Montero <Bonita.Montero@gmail.com>: Apr 18 02:20PM +0200

Am 17.04.2023 um 17:33 schrieb Bonita Montero:
 
>     }
>     return min;
> }
 
Comparing min against the next value may make a object conversion
necessary and sometimes the type of the next compared valie is the
common type. So I decided to check this for each "iteration":
 
template<typename ... TS>
common_type_t<TS ...> xmin( TS const &... values )
{
using ct_t = common_type_t<TS ...>;
ct_t min = get<0>( tuple<TS ...>( values ... ) );
auto nextMin = [&]<typename T>( T const &value )
{
if constexpr( same_as<T, common_type_t<TS ...>> )
if( value < min )
min = value;
else;
else
{
ct_t cvtValue( value );
if( cvtValue < min )
min = cvtValue;
}
};
(nextMin( values ), ...);
return min;
}
 
I guess there's nothing more to improve.
David Brown <david.brown@hesbynett.no>: Apr 18 09:12AM +0200

On 18/04/2023 01:11, Sam wrote:
> bowels, in certain situations. As far as I can decipher the bug updates
> they fixed it by tweaking std::vector's code to avoid triggering the
> spurious warning; but the warning was bogus.
 
Fair enough - I've said what I think, but I am not at all sure I was
accurate. I suggest you file a bug in gcc, and/or make a post in the
gcc help or development mailing lists, and see what the gcc folks say
about it. They know more about the fine details of C++ than the great
majority of people - more than most of the experts in this newsgroup,
and certainly far more than me. And of course they understand gcc
better. Compiler warnings are not always an exact science - occasional
false positives and false negatives are unavoidable, but reporting
suspected errors back to the developers is always useful.
Sam <sam@email-scan.com>: Apr 18 06:58AM -0400

David Brown writes:
 
> are not always an exact science - occasional false positives and false
> negatives are unavoidable, but reporting suspected errors back to the
> developers is always useful.
 
Which is what I did yesterday, and it turned out that I wasn't the only one
to encounter this. This warning is bogus. The capsule summary is that all
that gcc was doing is checking if a function parameter is a reference, and
the function returns the same type, a reference, and if the actual function
parameter in a call is a temporary. It's not even looking at what the
function is doing. Under these circumstances this warning is issued. That's
supposed to be its purpose.
 
I'm all for static analysis, but this one tips the scales in the other
direction, this warning creates too much useless noise, and false positives.
On a case by case basis, if I don't feel like tweaking the code to avoid it
I'll just turn off this warning, entirely.
Frederick Virchanza Gotham <cauldwell.thomas@gmail.com>: Apr 18 02:23AM -0700

On Monday, April 17, 2023 at 10:04:11 AM UTC+1, Frederick Virchanza Gotham wrote:
 
> Over on the mailing list for the Standard, Edward Catmur posted similar code to yours except he used a mutex.
 
So far there have been two implementations of generating a thunk for a lambda:
(a) Write the thunk as machine code onto the stack, and execute the stack
(b) Have a global pool of instantiations of a template function, and manage a global array of pointers associated 1:1 with the instantiations of the template function
 
The drawback of A is that the stack must be executable.
 
The drawback of B is that if you want to ensure 36 levels of recursion, the executable file will contain 36 thunks, and that you need to lock and unlock a mutex.
 
Today I've come up with a third solution, which only has one thunk in the final executable file, and which can handle as many threads and as much recursion as your heap allows.
 
What I've done is keep a global map of frame pointers to lambda objects. I got it working:
 
https://godbolt.org/z/1WzGq7cj9
 
And here it is copy-pasted:
 
#include <cassert> // assert
#include <cstddef> // size_t
#include <cstdlib> // abort (if mutex fails to lock)
#include <mutex> // mutex, lock_guard
#include <utility> // index_sequence, make_index_sequence, forward, declval
#include <map> // map
#include <iterator> // prev, next
 
namespace std {
extern void *frame_pointer(void);
}
 
// The next 5 lines are an x86_64 assembler implementation of std::frame_pointer
__asm__(
"_ZSt13frame_pointerv:\n" // mangled name of std::frame_pointer
" mov %rbp, %rax\n"
" ret\n"
);
 
// The stack grows negatively on the x86_64, so we use the following
// function to compare two frame pointers:
bool IsFurtherFromTop(void const *const p, void const *const q)
{
// returns true if 'q' is closer to the top of the stack
return p > q;
}
 
// The next three templates: 'ToFuncPtr', 'ToReturnType', 'IsNoExcept'
// are just helpers to make this all possible. You can scroll past them
// to Line #78
 
namespace detail {
 
// The following template turns a member function pointer into a
// normal function pointer, but we only want it to use decltype on it
template<typename ReturnType, class ClassType, bool b_noexcept, typename... Params>
ReturnType (*ToFuncPtr(ReturnType (ClassType::*)(Params...) const noexcept(b_noexcept)))(Params...) noexcept(b_noexcept)
{
return nullptr; // suppress compiler warning
}
// and also the non-const version for non-const member functions (or mutable lambdas):
template<typename ReturnType, class ClassType, bool b_noexcept, typename... Params>
ReturnType (*ToFuncPtr(ReturnType (ClassType::*)(Params...) noexcept(b_noexcept)))(Params...) noexcept(b_noexcept)
{
return nullptr; // suppress compiler warning
}
 
// The following template isolates the return type of a member function pointer,
// but we only want it to use decltype on it. I tried using 'std::result_of_t'
// instead but I get a compiler error for the lambda being an incomplete type
template <typename ReturnType, class ClassType, typename... Params>
ReturnType ToReturnType(ReturnType (ClassType::*)(Params...) const)
{
return std::declval<ReturnType>(); // suppress compiler warning
}
// and also the non-const version for non-const member functions (or mutable lambdas):
template <typename ReturnType, class ClassType, typename... Params>
ReturnType ToReturnType(ReturnType (ClassType::*)(Params...))
{
return std::declval<ReturnType>(); // suppress compiler warning
}
 
// The following template determines whether a non-static member function is noexcept
template<typename ReturnType, class ClassType, bool b_noexcept, typename... Params>
consteval bool IsNoExcept(ReturnType (ClassType::*)(Params...) const noexcept(b_noexcept))
{
return b_noexcept;
}
// and also the non-const version for non-const member functions (or mutable lambdas):
template<typename ReturnType, class ClassType, bool b_noexcept, typename... Params>
consteval bool IsNoExcept(ReturnType (ClassType::*)(Params...) noexcept(b_noexcept))
{
return b_noexcept;
}
 
} // close namespace 'detail'
 
template<typename LambdaType>
class thunk {
protected:
using size_t = std::size_t;
using R = decltype(detail::ToReturnType(&LambdaType::operator()));
using FuncPtr = decltype(detail::ToFuncPtr (&LambdaType::operator())); // preserves the 'noexcept'
 
// In the map on the next line, we keep track of which frame pointers
// are associated with which lambda objects
// map.begin()->first == frame pointer
// map.begin()->second == address of lambda object
inline static thread_local std::map<void*,LambdaType*> frame_pointers_for_lambdas;
 
public:
explicit thunk(LambdaType &arg) noexcept {
try {
assert( nullptr == frame_pointers_for_lambdas[ std::frame_pointer() ] );
frame_pointers_for_lambdas[ std::frame_pointer() ] = &arg;
}
catch(...) {
assert(nullptr == "failed to add frame pointer to map");
std::abort(); // ifdef NDEBUG
}
}
~thunk() noexcept {
assert( nullptr != frame_pointers_for_lambdas[ std::frame_pointer() ] );
frame_pointers_for_lambdas.erase(std::frame_pointer());
}
FuncPtr get() const noexcept {
return &invoke;
}
operator FuncPtr() const noexcept { return this->get(); }
 
protected:
 
template<typename... A> static R invoke(A... a) noexcept(detail::IsNoExcept(&LambdaType::operator()))
{
assert( frame_pointers_for_lambdas.size() >= 1u );
 
void *const fp = std::frame_pointer();
 
assert( false == IsFurtherFromTop(fp, frame_pointers_for_lambdas.begin()->first) );
 
size_t i;
for ( i = 1u; i < frame_pointers_for_lambdas.size(); ++i )
{
if ( IsFurtherFromTop(fp, std::next(frame_pointers_for_lambdas.begin(),i)->first) )
{
break;
}
}
 
LambdaType &mylambda = *std::next(frame_pointers_for_lambdas.begin(),i - 1u)->second;
return mylambda(std::forward<A>(a)...);
}
 
thunk(void) = delete;
thunk(thunk const & ) = delete;
thunk(thunk &&) = delete;
thunk &operator=(thunk const & ) = delete;
thunk &operator=(thunk &&) = delete;
thunk const *operator&(void) const = delete; // just to avoid confusion
thunk *operator&(void) = delete; // just to avoid confusion
};
 
// ========================================================
// And now the test code.............
// ========================================================
 
#include <cstring> // strcpy, strcat
#include <cstdio> // sprintf
#include <thread> // jthread
#include <stop_token> // stop_token
#include <iostream> // cout, endl
 
using std::cout, std::endl;
 
bool SomeLibraryFunc( bool (*arg)(int), int val )
{
return arg(val);
}
 
void RecursiveFunc(int arg)
{
if ( arg < 0 ) return;
 
thread_local char str_buf[128u]; // so we don't need a mutex for cout
 
auto f = [arg](int const i) -> bool
{
std::sprintf(str_buf, "Hello %i thunk\n",i);
cout << str_buf;
return i < 2;
};
 
bool const retval = SomeLibraryFunc( thunk(f), arg );
 
std::strcpy(str_buf, "Returned: ");
std::strcat(str_buf, retval ? "true\n" : "false\n");
cout << str_buf;
 
RecursiveFunc(--arg);
}
 
void ThreadEntryPoint(std::stop_token,int const arg)
{
RecursiveFunc(arg);
}
 
void Spawn_All_Thread_And_Join(int arg)
{
std::jthread mythreads[4u];
 
for ( auto &t : mythreads )
{
t = std::jthread(ThreadEntryPoint,arg += 7);
}
}
 
int main(int argc, char **argv)
{
while ( argc++ < 3 ) Spawn_All_Thread_And_Join(argc);
}
You received this digest because you're subscribed to updates for this group. You can change your settings on the group membership page.
To unsubscribe from this group and stop receiving emails from it send an email to comp.lang.c+++unsubscribe@googlegroups.com.

No comments: