I cannot achieve the effect in title, as implemented in the following snippet:
#include <functional>
#include <iostream>
#define USEFUNPTR
#define USESTDFUN
class Dummy {
public:
#ifdef USEFUNPTR
void (*Foo)() noexcept = nullptr;
#endif
#ifdef USESTDFUN
std::function<void() noexcept> Bar;
#endif
void InitFuns() {
#ifdef USEFUNPTR
Foo = []() noexcept { std::cout << "Foo\n" << std::endl; };
#endif
#ifdef USESTDFUN
Bar = []() noexcept { std::cout << "Bar\n" << std::endl; };
#endif
};
~Dummy() noexcept = default;
};};
Live
with USEFUNPTR
defined:
In C++14, msvc refuses the function pointer version:
error C2440: '=': cannot convert from 'Dummy::InitFuns::<lambda_b40e7171393910f4bba39b7be19bf362>' to 'void (__cdecl *)(void) noexcept'
with USESTDFUN
defined:
In C++17 gcc
, clang
and msvc
reject the std::function
version:
gcc
:
error: field 'Bar' has incomplete type 'std::function<void() noexcept>' 13 | std::function<void() noexcept> Bar;
msvc
: error C2338: static_assert failed: 'std::function does not accept noexcept function types as template arguments.'
msvc
is more explicit and indicates that, from C++17, std::function
seems unable to wrap a noexcept
function (there is indeed no prototype of std::function
with noexcept
and this specifier is part of the type from C++17, if I understood correctly (from some other SO post):
error C2440: '=': cannot convert from 'Dummy::InitFuns::<lambda_b40e7171393910f4bba39b7be19bf362>' to 'void (__cdecl *)(void) noexcept'
How can I achieve this functionality in a way that works in all C++>=14, and with all compilers?
(In an actual usecase, the InitFuns may have runtime and/or compile time parameters and the lambda definition will depend on these parameters, yet the code that will call Foo
or Bar
is unaware of these parameters).
NB I found several posts indicating that a lambda cannot decay to a function pointer member but I didn't understand why and if they were a workaround to achieve this functionnality.
There is a kind of workaround, that lets you detect if the lambda is noexcept before you assign it to a std::function<void()>. Another option is to wrap a try/catch block around any noexcept function (runtime detections, so suboptimal)
Example here :
#include <functional>
#include <type_traits>
#include <iostream>
#include <stdexcept>
// noexcept will not be picked up by std::function
// but with SFINAE you can detect the noexcept
using function_ptr_t = void (*)() noexcept;
namespace traits
{
template<typename fn_t>
using is_noexcept_fn = std::enable_if_t<noexcept(std::declval<fn_t>()()), fn_t>;
template<typename fn_t>
static constexpr bool is_noexcept_fn_ptr()
{
return std::is_convertible_v<fn_t, function_ptr_t> && noexcept(std::declval<fn_t>());
}
template<typename fn_t>
using is_noexcept_fn_ptr_t = std::enable_if_t<is_noexcept_fn_ptr<fn_t>(), fn_t>;
}
class Dummy
{
public:
// Only accept the lambda (function object) if it has a noexcept operator()
template<typename fn_t, typename enable_t = traits::is_noexcept_fn_ptr_t<fn_t>>
explicit Dummy(fn_t fn) :
m_fn{ fn }
{
}
void operator()() noexcept
{
m_fn();
}
private:
std::function<void()> m_fn;
};
class Dummy2
{
public:
// other option is to actually catch all exceptions for
// functions that are not noexcept
template<typename fn_t>
explicit Dummy2(fn_t fn)
{
if constexpr (noexcept(fn()))
{
m_fn = fn;
}
else
{
auto lambda = [=]()
{
try
{
fn();
}
catch (...)
{
std::cout << "unhandled exeption caught\n";
}
};
m_fn = lambda;
}
}
void operator()() noexcept
{
m_fn();
}
private:
std::function<void()> m_fn;
};
class Dummy3
{
public:
// Only accept a function pointer if it is a noexcept operator()
template<typename fn_t, typename enable_t = traits::is_noexcept_fn_ptr_t<fn_t>>
explicit Dummy3(fn_t fn) :
m_fn{ fn }
{
}
void operator()() noexcept
{
m_fn();
}
private:
function_ptr_t m_fn;
};
void test() noexcept
{
std::cout << "free function\n";
};
int main()
{
//Dummy d1{ [] { std::cout << "does not compile\n"; } };
Dummy d{ []() noexcept { std::cout << "does compile\n"; } };
d();
Dummy2 d2{ [] { throw std::runtime_error{"oops"}; } };
d2();
Dummy3 d3_lambda{ []() noexcept { std::cout << "using lmabda's function pointer\n"; } };
d3_lambda();
Dummy3 d3_free_function(test);
d3_free_function();
// indeed fails to compile
//Dummy3 d3_lambda_fail{ [] { std::cout << "using lmabda's function pointer\n"; } };
//d3_lambda();
return 0;
}