错误封装的模式
  我这里把它叫做一种模式,所以人们不必害怕担心。后面,我会给它一种更好的命名,所以请不要着急。
  错误封装的主旨是创建一种封装来包含错误消息或者错误的返回值。我们通常会选择字符串而不是其他,因为这也并不容易实现。我们尽力保证语法的可读性,可理解,并且容易应用。我们不处理拷贝构造或者多参数函数及返回值,这里仅给出一个尽可能简单的例子。
  让我们以下面的例子开始:
  E<int> find_slash(const char* str)
  {
  int i = 0;
  while (str[i] && str[i] != '/')
  i++;
  if (str[i] == '')
  return fail<int>("Error message");
  //True value
  return ret(i);
  }
  // . . .
  auto v = find_slash(string);
  if(!v)
  {
  //Handle exception
  }
  乍一看,这里有点类似C语言的风格,但是不是,为表明这一点,请看接下来的多个函数调用例子:
  E<int> find_slash(const char*);
  E<int> do_some_arithmetic(int);
  E<std::string> format(int);
  E<void> display(std::string);
  auto v = ret(string)
  .bind(find_slash)
  .bind(do_some_arithmetic)
  .bind(format)
  .bind(display);
  if(!v)
  {
  //Handle error
  }
  好了,这里发生了什么?bind是一个成员函数来绑定你的函数调用,试着去应用它。如果错误装箱里面含有一个值,那么它应用于函数调用,继续返回一个错误装箱(编译器不允许你返回一个不带错误装箱的函数)。
  所以,我们链式调用了find_slashe,do_some_arithmetic, format和display.它们都不处理错误装箱,由于bind函数的作用,我们将函数E<something_out> f(something_in)返回结果给E<something_out> f(E<something_in>)函数做参数。
  这里的好处是什么?
  再一次,函数逻辑(调用链)和错误处理分离了。和异常一样,我们可以简单读一下函数调用链来了解代码逻辑,而不用关心执行是在哪里被中断的。事实上,函数调用链可以在任何调用时被中断。但是我们可以认为没有错误发生,如果我们的逻辑是正确的,可以很快速检查。
  当然,类型推导会阻止你在调用display之后继续进行绑定。所以我们也没有失去类型能力。
  注意,我们没有在其他地方调用这些函数,我们在后将这些方法组装在一起。这里是关键,你应该编写一些小的模块函数(另外,注意:你应该编写模板函数使其工作)接收一个值,然后计算一个新值或者返回失败。在每一步中,你都不需要考虑可能出现错误导致你的控制流中断,并且校验你是否在一个有效的状态上(异常安全基于查询每个函数调用,指出函数是否中断你的控制流程,如果出现异常会发生什么),基于这一点,这样做更安全。
  和异常一样,我们可以处理很详细的信息,尽管这里我们编写的是一个偏模板函数,所以也容易理解一些。
  我们可以很容易放置异常处理逻辑,把它放在函数调用链之后(除非这个返回值还需要进一步被链接)。现在,我们有一个大的的执行流,没有中断,使用小的函数处理流程,容易定位。当需要添加一个新的错误时,你只需找到那些函数,通过函数调用链,你可以直接定位到处理位置,并根据需要添加。大型项目变得更加的线性化,并且更易读。
  这样做有什么不足?
  首先,这是一个新的处理方式,并且和C++的方式不兼容。这不是一个标准处理方法,当你使用stl时,你仍然需要使用异常。
  对于我来说,这样做还是有点冗长。需要显式编写fail<int>(…)的模板推导显得有点怪异,如果你有个多态错误类型更糟了,你不得不这样写fail<return_type, error_type>("...").
  当函数有多个参数时编写也很困难,在其他一些语言中,可以使用适用类型和抽象类型很好地解决这个问题,不过这在C++中不会提供。我想更适合使用bind2(E<a>, E<b>, f)和bind3(E<a>, E<b>, E<c>, f),可变模板参数功能更有用。
  为获取封装错误中的值,我们需要检查这个值是否是有效值,接着调用一个“to_value”方法。我们没办法不通过检查来做到这一点。我们希望的是“解构”一个对象,不过这在C++中不支持,这也不是一些可以说“我们把它加入到下一个标准”的特性。
  目前为止,我不知道读者是否有方法将其适配到成员函数中,如果你有想法,请测试一下,如果可以,请告知我们。
  实现原子错误处理
  我实现了它,我定义了这个黑魔法的名字——“原子化”,你可以认为“原子化”是一个对值和错误上下文的装箱,比如,一个box包含一个值或者什么也不包含是一个原子组(这里作为一个练习,你可以试着实现一下)。
  有点奇怪的是,从某个角度来说队列是一个原子组,他们拥有一个上下文的值。
  让我们从上面的E模版类实现开始,这里使用了C++11标准中的decltype和auto -> decltype类型,允许自动推导得到表达式的类型,这非常有用。
  这里的bind函数有点怪异,但是他实现了我刚才提到的内容。
/*
This is the "Either String" monad, as a way to handle errors.
*/
template
<typename T>
class E
{
private:
//The value stored
T m_value;
//The error message stored
std::string m_error;
//A state. True it's a value, false it's the message.
bool m_valid;
E()
{}
public:
//Encapsulate the value
static E ret(T v)
{
E box;
box.m_value = v;
box.m_valid = true;
return box;
}
//Encapsulate an error
static E fail(std::string str)
{
E box;
box.m_error = str;
box.m_valid = false;
return box;
}
//True if it's a valid value
operator bool() const
{
return m_valid;
}
//Deconstruct an E to a value
T to_value() const
{
//It's a programmer error, it shouldn't happen.
if (!*this)
{
std::cerr << "You can't deconstruct to a value from an error" << std::endl;
std::terminate();
}
return m_value;
}
//Deconstruct an E to an error
std::string to_error() const
{
//It's a programmer error, it shouldn't happen.
{
std::cerr << "You can't deconstruct to an error from a value" << std::endl;
std::terminate();
}
return m_error;
}
friend std::ostream& operator<< (std::ostream& oss, const E<T>& box)
{
if (box)
oss << box.m_value;
else
oss << box.m_error;
return oss;
}
template<typename F>
inline
auto bind(F f) -> decltype(f(m_value))
{
using type = decltype(f(m_value));
if (*this)
return f(m_value);
else
return type::fail(m_error);
}
};
  这里,我重载了<<运算符,所以导出装箱中的内容更容易一些。我们并不是一定需要它,在“真”值时去掉这一点也更好一些。
  这里的例子,我们需要一个“E<void>”类型,但是它可能不一定使用。我们需要为void实现一个特别的重载,这里其实也是一样的,只不过期望的值是一个“空箱”。
/*
Special instance for void
*/
template<>
class E<void>
{
private:
std::string m_error;
bool m_valid;
E()
{}
public:
//Encapsulate the value
static E ret()
{
E box;
box.m_valid = true;
return box;
}
//Encapsulate an error
static E fail(std::string str)
{
E box;
box.m_error = str;
box.m_valid = false;
return box;
}
//True if it's a valid value
operator bool() const
{
return m_valid;
}
//Déconstruct an E to a value
void to_value() const
{
//It's a programmer error, it shouldn't happen.
if (!*this)
{
std::cerr << "You can't deconstruct to a value from an error" << std::endl;
std::terminate();
}
}
//Deconstruct an E to an error
std::string to_error() const
{
//It's a programmer error, it shouldn't happen.
if (*this)
{
std::cerr << "You can't deconstruct to an error from a value" << std::endl;
std::terminate();
}
return m_error;
}
friend std::ostream& operator<< (std::ostream& oss, const E<void>& box)
{
if (box)
oss << "()";
else
oss << box.m_error;
return oss;
}
template<typename F>
inline
auto bind(F f) -> decltype(f())
{
using type = decltype(f());
if (*this)
return f();
else
return type::fail(m_error);
}
};
  我们没有提到ret和fail方法,事实上,它们只是对xxx::fail和xxx::ret函数的封装。
  /*
  Then, I introduced those simple functions, to reduce the
  call to something readable/writable
  */
  template <typename T>
  inline
  E<T> ret(T v)
  {
  return E<T>::ret(v);
  }
  template <typename T>
  inline
  E<T> fail(std::string err)
  {
  return E<T>::fail(err);
  }
  这里,你可以编译并执行一下上面的代码。
  如果你想要更多的,可以试试下面这个更具体一点的例子:
/*
Herecomeacaseofuse.
*/
//Whatauserwouldsee:
//Returnavalueinanerrorcontext
template<typenameT>inline
E<T>ret(Tv);
//FailinanerrorcontextoftypeT
template<typenameT>inline
E<T>fail(std::stringerr);
//Whatauserwouldwrite:
typedefstd::vector<std::string>vs;
typedefstd::stringstr;
//Parsea+-formatedstring.
//Ifaletterisprefixedby+,thenthefunctiontoupperisapplied.
//''tolowerisapplied.
//Nonalphabetical(+and-excepted)aren'talowed.
//Wordsarecutoneachspace''.Otherblankcharactersaren'talowed.
E<std::vector<std::string>>parse(std::stringstr)
{
intmode=0;
vsvec;
if(str.empty())
returnfail<vs>("Emptystringaren'tallowed");
std::stringstack;
for(inti=0;str[i]!='';i++)
{
switch(str[i])
{
case'-':
mode=1;
break;
case'+':
mode=2;
break;
case'':
{
if(!stack.empty())
vec.push_back(stack);
stack.resize(0);
mode=0;
break;
}
default:
{
if(!isalpha(str[i]))
returnfail<vs>("Onlyalphacharactersareallowed");
if(mode==1)
stack.push_back(tolower(str[i]));
elseif(mode==2)
stack.push_back(toupper(str[i]));
else
stack.push_back(str[i]);
mode=0;
break;
}
}
}
if(!stack.empty())
vec.push_back(stack);
returnret(vec);
}
//Takethefirstwordandappendittothebeginingofallotherwords.
//Vecshouldcontainatleastoneelement.
E<std::vector<std::string>>prefixy(std::vector<std::string>vec)
{
if(vec.empty())
returnfail<vs>("Can'taddprefixesonanemptytable");
std::stringprefix=vec.front();
vsout;
for(autos:vec)
{
if(prefix==s)
continue;
out.push_back(prefix+s+"^");
}
returnret(out);
}
//Concatenateallstringsasabigstring.Vecshouldcontaindata.
E<std::string>concat(std::vector<std::string>vec)
{
std::stringoutput;
if(vec.empty())
returnfail<str>("Emptyvectorsaren'tallowed");
for(autos:vec)
output+=s;
if(output.empty())
returnfail<str>("Nodatafound");
returnret(output);
}
intmain()
{
typedefstd::strings;
//Parsesomestring,showhowerrorinterruptcomputationofthe"chain".
std::cout<<ret((s)"+hello-WORLD").bind(parse).bind(prefixy).bind(concat)<<std::endl;
std::cout<<ret((s)"+helloHelloHello").bind(parse).bind(prefixy).bind(concat)<<std::endl;
std::cout<<ret((s)"+").bind(parse).bind(prefixy).bind(concat)<<std::endl;
std::cout<<ret((s)"+hi").bind(parse).bind(prefixy).bind(concat)<<std::endl;
//Playwithlambdato"replace"avalueifit'snotanerror.
std::cout<<ret((s)"Somestring").bind([](conststd::string&){returnfail<s>("Failed");});
std::cout<<ret(23).bind([](constint){returnret(42);});
std::cout<<fail<int>("NaN").bind([](constint){returnret(42);});
return0;
}