从函数内部推断函数返回类型

Infere the function return type from inside the function

本文关键字:函数 返回类型 内部      更新时间:2023-10-16

我有一个class,非常遗憾,它依赖于两步初始化。这意味着在构建之后,对象仍然没有准备好使用,除非调用了初始化方法:

class A
{
public:
A();
bool init();
private:
bool m_is_initialized;
};

类的每个其他方法都应遵循此策略:如果在类尚未初始化时调用该方法,则该方法应停止执行,并在类特定的通道上记录错误。

问题是有些方法有返回类型。这种情况下的策略是返回返回类型的默认构造值。

这个想法是有一个简单的宏,可以在每个方法实现的开始调用,比如:

#define CHECK_INITIALIZED                                                  
if ( !m_is_initialized )                                                   
{                                                                          
LOG_E( m_channel, "%s object not initialized.", __PRETTY_FUNCTION__ ); 
assert( false );                                                       
return;                                                                
} 

顺便说一句,return语句仅对void函数有效,并不适用于所有情况。有没有一种方法可以推断出宏在其中展开的函数的返回类型T,这样我就可以返回T(),并使宏在任何地方都可用?

编辑:请注意,由于项目限制,遗憾的是不能使用异常。

为什么要使用宏?模板可以很好地处理这个问题。

struct has_init_check
{
protected:
template<class F>
auto if_ready(F&& f)
{
if (m_is_initialized)
{
f();
}
else
{
// log the error here
}
} 
void notify_initialized()
{
m_is_initialized = true;
}
private:
bool m_is_initialized = false;
};
class A 
: has_init_check
{
public:
A();
bool init() { notify_initialized(); }
int foo();
void bar();
};
int A::foo()
{
int result = 0; // default value
if_ready([&]
{
// logic here
result = 10;
});
return result;    
}
void A::bar()
{
if_ready([]
{
// logic here
});
}

您可以使用return {};来表示您想要一个默认的初始化返回类型。尽管在类型不是默认可构造的、您试图返回引用或返回类型为void的情况下,这种方法还是失败了。

另一种选择是使用boost::optional/std:optional作为所有函数与return {};组合的返回类型。这允许您在不执行任何操作时返回默认的可选项(因此可选项为空)。

另一种选择是将返回值传递给宏,并将其用于之类的返回

#define CHECK_INITIALIZED(default_return_value)                            
if ( !m_is_initialized )                                                   
{                                                                          
LOG_E( m_channel, "%s object not initialized.", __PRETTY_FUNCTION__ ); 
return default_return_value;                                                                
} 

另一个答案,另一种方法。

不允许出现异常,但我们仍然可以通过使用变量(错误、对象)在构建时发现初始化失败。

我们知道,我们对象的所有成员都是notrow_constructable(这是一个约束)。因此,它们也必须是不可移动的。

因此,我们可以使用variant和optional的组合来管理对象构造,并在失败时记录/停止。

现在,在实现方法时没有运行时开销。

#include <variant>
#include <optional>
#include <string>
#include <cstdlib>
#include <iostream>
#include <type_traits>

struct construction_error
{
construction_error(std::string s)
: message_(s)
{}
const std::string message() const {
return message_;
}
std::string message_;
};
template<class T> using construction_result = std::variant<construction_error, T>;
template<class T> struct tag {};
template<class T, class...Args>
auto construct(tag<T>, Args&&...args) -> construction_result<T>
{
auto x = T(std::forward<Args>(args)...);
if (auto result = x.init())
{
return std::move(result).value();
}
else
{
return std::move(x);
}
}
class A 
{
public:
A() noexcept { std::cout << "speculative construction" << std::endl; }
std::optional<construction_error> init() noexcept { 
if (rand() < RAND_MAX / 2)
{
return construction_error("failed to construct an A");
}
else
{
// complete the construction
return {};
}
}
int foo();
void bar();
};
int A::foo()
{
std::cout << __func__ << std::endl;
// logic here
return 10;
}
void A::bar()
{
std::cout << __func__ << std::endl;
// logic here
}
void do_thing(A a, A b, A c)
{
a.foo();
b.foo();
c.foo();
a.bar();
b.bar();
c.bar();
}
template<class T>
void maybe_report_failure(const T&)
{
}
void maybe_report_failure(construction_error const& cf)
{
std::cout << "construction failure: " << cf.message() << std::endl;
}
int main()
{
for (int i = 0 ; i < 100 ; ++i)
{
auto maybe_a_1 = construct(tag<A>());
auto maybe_a_2 = construct(tag<A>());
auto maybe_a_3 = construct(tag<A>());
auto action = [](auto&&...as)
{
constexpr bool good = (std::is_same_v<std::decay_t<decltype(as)>, A> && ...);
if constexpr (good)
{
do_thing(std::move(as)...);
}
else
{
(maybe_report_failure(as), ...);
}
};
std::visit(action, 
std::move(maybe_a_1), 
std::move(maybe_a_2), 
std::move(maybe_a_3));
}
}

http://coliru.stacked-crooked.com/a/397427a89afa728a

这应该适用于void、默认可构造+可移动和引用类型(未测试:):

#define CHECK_INITIALIZED_WITH_RETURN(R)                                                 
if ( !m_is_initialized )                                                   
{                                                                          
LOG_E( m_channel, "%s object not initialized.", __PRETTY_FUNCTION__ ); 
assert( false );                                                       
static std::conditional_t<std::is_same_v<R,void>,int,std::decay_t<R>> some_default{}; 
return R(some_default);  
}

其中R可以是空的,T,T&,。。。(假设默认构建的静态没有令人讨厌的副作用,并且以"理智"的方式使用…)