Quick Links:

bal | bbl | bdl | bsl

Namespaces

Component bslmf_invokeresult
[Package bslmf]

Determine the result type of an invocable expression. More...

Namespaces

namespace  bslmf
namespace  bsl

Detailed Description

Outline
Purpose:
Determine the result type of an invocable expression.
Classes:
bsl::invoke_result Metafunction to determine invocation result type
bslmf::InvokeResultDeductionFailed Returned on failed result deduction
Macros:
BSLMF_INVOKERESULT_SUPPORT_CPP17_SEMANTICS defined if SFINAE-friendly
See also:
bslstl_invoke
Description:
This component provides a metafunction bsl::invoke_result that determines, at compile time, the type returned by invoking a callable type, including pointer-to-function, pointer-to-member function, pointer-to-member object (returns the object type), or functor class, and a class bslmf::InvokeResultDeductionFailed that is returned when the invocation return type cannot be determined (C++03 only). For a set of types F, T1, T2, and T3, bsl::invoke_result<F, T1, t2, T3>type is roughly the type of the return value obtained by calling an object of type F with arguments of type T1, T2, and T3, respectively. However, invoke_result goes beyond function-like objects and deduces a return type if F is a pointer to function member or data member of some class C and T1 is a type (derived from) C, pointer to C, or smart-pointer to C. (See precise specification, below).
The interface and functionality of bsl::invoke_result is intended to be identical to that of the C++17 metafunction, std::invoke_result except that invalid argument lists are detected in C++11 and later, but not in C++03. In C++03, invalid arguments lists will result in a compilation error (instead of simply missing type) in the remaining cases. Some other functionality is lost when compiling with a C++03 compiler -- see the precise specification, below.
C++17 Semantics Detection:
This component defines the macro BSLMF_INVOKERESULT_SUPPORT_CPP17_SEMANTICS if bsl::invoke_result behaves according to the C++17 specification of std::invoke_result, which is elaborated below. This macro is defined as long as the compiler supports the decltype specifier, which is generally available in C++11 and later compilation modes.
Precise specification:
The C++11 and C++14 standard defines the pseudo-expression INVOKE (f, t1, t2, ..., tN), as follows:
  • (t1.*f)(t2, ..., tN) when f is a pointer to a member function of a class T and t1 is an object of type T or a reference to an object of type T or a reference to an object of a type derived from T;
  • ((*t1).*f)(t2, ..., tN) when f is a pointer to a member function of a class T and t1 is not one of the types described in the previous item;
  • t1.*f when N == 1 and f is a pointer to member data of a class T and t1 is an object of type T or a reference to an object of type T or a reference to an object of a type derived from T;
  • (*t1).*f when N == 1 and f is a pointer to member data of a class T and t1 is not one of the types described in the previous item;
  • f(t1, t2, ..., tN) in all other cases.
Given types F, T1, T2, ..., TN corresponding to the expressions f, t1, t2, ..., tN in the definition of INVOKE, the type produced by bslmf::ResultType<F, T1, T2, ..., TN>type is generally the type of the psuedo-expression INVOKE (f, t1, t2, ..., tN), with some limitations in C++03, as described below.
Because C++03 does not support decltype, there are circumstances in which bsl::invoke_result is not able to deduce the return type of an invocable object of class type (i.e., a functor). If R is the type of the INVOKE expression, then ideally type is R. However the C++03 version of bsl::invoke_result determines type as follows:
  1. If there exists a user-defined specialization of bsl::invoke_result<F, T1, T2, ... TN>, then type is determined by the specialization, regardless of correctness. (This rule is true of C++11 and later, as well.)
  2. Otherwise, if F is a function type, pointer to function type, pointer to member function type, pointer to member object type, or reference to any of these (i.e, F is anything other than a class type or reference to class type), then type is R.
  3. Otherwise, if R is o a fundamental type, o a pointer to (possibly cv-qualified) void or fundamental type, o an lvalue reference to any of the above types (possibly cv-qualified), o bsl::nullptr_t, or o void, then type is R.
  4. Otherwise, if F is a class type with member result_type, then type is F::result_type. Note that bsl::invoke_result cannot deduce different result types for different overloads of operator() in this case.
  5. Otherwise, if F is a class type with member type ResultType, then type is F::ResultType. Note that bsl::invoke_result cannot deduce different result types for different overloads of operator() in this case.
  6. Otherwise, type is bslmf::InvokeResultDeductionFailed. The benefit of this placeholder over a compilation error is that invoke_result is often used in a context where the return value will eventually be discarded. Thus, generating a useless type is often harmless. In cases where it is not harmless, the placeholder type will almost certainly result in a compilation error in the surrounding code.
If the callable type is a pointer-to-member (data or function), invalid argument lists are not detected. Thus, there is a small chance that invalid code will compile successfully, though it is hard to see now this would be harmful, since determining the return type of an expression is not very useful if the expression is not eventually evaluated, which will certainly produce the expected compilation error for invalid argument lists.
Usage Example:
Suppose we want to create a wrapper that executes an invocable object and sets a done flag. The done flag will not be set if the invocation exits via an exception. The wrapper takes an invocable f and an argument x and evaluates f(x), returning the result. In the absence of C++14 automatically-deduced function return declarations, we use bsl::invoke_result to deduce the return type of f(x).
First, we write the wrapper template as follows:
  template <class FT, class XT>
  typename bsl::invoke_result<FT, XT>::type
  invokeAndSetFlag(bool *done, FT f, XT x)
      // Return 'f(x)' and set '*done' to true if no exception.
  {
      typedef typename bsl::invoke_result<FT, XT>::type ResultType;
      *done = false; // Clear flag in case of exception
      ResultType result = f(x);
      *done = true;  // Set flag on success
      return result;
  }
Note that additional metaprogramming would be required to make this template work for return type void; such metaprogramming is beyond the scope of this usage example.
Then we define a couple of simple functors to be used with the wrapper. The first functor is a simple template that triples its invocation argument:
  template <class t_TP>
  struct Triple {
      // Functor that triples its argument.

      t_TP operator()(t_TP v) const { return static_cast<t_TP>(v * 3); }
          // Return three times the specified 'v' value.
  };
Next, we define a second functor that returns an enumerator ODD or EVEN, depending on whether its argument is exactly divisible by 2. Since the return type is not a fundamental type, this functor indicates its return type using the ResultType idiom:
  enum EvenOdd { e_EVEN, e_ODD };

  struct CalcEvenOdd {
      // Functor that determines whether its argument is odd or even.

      typedef EvenOdd ResultType;

      EvenOdd operator()(int i) const { return (i & 1) ? e_ODD : e_EVEN; }
          // Return 'e_ODD' if the specified 'i' is odd; otherwise return
          // 'e_EVEN'
  };
Finally, we can invoke these functors through our wrapper:
  int main()
      // Run the usage example.
  {
      bool done = false;

      Triple<short> ts = {};
      short         r0 = invokeAndSetFlag(&done, ts, short(9));
      assert(done && 27 == r0);

      CalcEvenOdd ceo = {};
      done            = false;
      EvenOdd r1      = invokeAndSetFlag(&done, ceo, 5);
      assert(done && e_ODD == r1);

      done = false;
      EvenOdd r2 = invokeAndSetFlag(&done, ceo, 8);
      assert(done && e_EVEN == r2);

      return 0;
  }