-
Notifications
You must be signed in to change notification settings - Fork 384
Type Constraints Unraveled
If one is not familiar with C++ template overload resolution, the use of enable_if
and enable_if_t
to constrain template parameters can be a little hard to follow. While these are provided by C++11 and C++14 respectively, they are trivially implemented as follows.
// C++11 implementation of std::enable_if.
template<bool Bool, typename Type = void>
struct enable_if
{
};
template<class Type>
struct enable_if<true, Type>
{
typedef Type type;
};
// C++14 implementation of use std::enable_if_t.
template <bool Bool, typename Type = void>
using enable_if_t = typename enable_if<Bool, Type>::type;
The following helpers combine signed-ness with integer-ness (i.e. floating point excluded).
#include <type_traits>
template <typename Type>
using if_integer = enable_if_t<
std::numeric_limits<Type>::is_integer, bool>;
template <typename Type>
using if_signed_integer = enable_if_t<
std::numeric_limits<Type>::is_integer &&
std::is_signed<Type>::value, bool>;
template <typename Type>
using if_unsigned_integer = enable_if_t<
std::numeric_limits<Type>::is_integer &&
!std::is_signed<Type>::value, bool>;
The std::numeric_limits<Type>::is_integer
template requires C++11 for long long
, unsigned long long
and some character types. For older versions of C++ this may be implemented with simple custom templates that enumerate these types.
When the Bool
parameter of enable_if_t<bool Bool, typename Type>
is true, enable_if_t
resolves to the specified Type
, in the above cases bool
. Otherwise it resolves to the undefined expression (struct enable_if{})::type
. The former is then defaulted (i.e. using = true
or = false
) so that it is not required. The latter will not match any expression, so that case is excluded.
The following is_negative
overloads provide an example.
template <typename Integer, if_signed_integer<Integer> = true>
bool is_negative(Integer value)
{
return value < 0;
}
template <typename Integer, if_unsigned_integer<Integer> = true>
bool is_negative(Integer value)
{
return false;
}
These reduce to the following.
// if (std::numeric_limits<Type>::is_integer && std::is_signed<Integer>::value)
template <typename Integer, bool = true>
bool is_negative(Integer value)
{
return value < 0;
}
// if (std::numeric_limits<Type>::is_integer && !std::is_signed<Integer>::value)
template <typename Integer, bool = true>
bool is_negative(Integer value)
{
return false;
}
A side effect of this technique is that the signature of is_negative
is actually is_negative<Integer, bool>(Integer value)
, where a bool
value is ignored if specified.
Another approach is to reply on enable_if
alone.
template <typename Integer,
typename Unused = enable_if<std::numeric_limits<Type>::is_integer>::type>
bool is_odd(Integer value)
{
return (value % 2) != 0;
}
As the Unused
type may be unnamed, this may also be written as follows.
template <typename Integer,
typename = enable_if<std::numeric_limits<Type>::is_integer>::type>
bool is_odd(Integer value)
{
return (value % 2) != 0;
}
This resolves to the following.
// if (std::numeric_limits<Type>::is_integer)
template <typename Integer,
typename = (struct enable_if{ typedef bool type; })::type>
bool is_odd(Integer value)
{
return (value % 2) != 0;
}
// if (!std::numeric_limits<Type>::is_integer)
template <typename Integer,
typename = (struct enable_if{})::type>
bool is_odd(Integer value)
{
return (value % 2) != 0;
}
And these reduce to the following.
// if (std::numeric_limits<Type>::is_integer)
template <typename Integer, typename = bool>
bool is_odd(Integer value)
{
return (value % 2) != 0;
}
// if (!std::numeric_limits<Type>::is_integer)
template <typename Integer, typename = undefined>
bool is_odd(Integer value)
{
return (value % 2) != 0;
}
The bool
type is inferred from the expression std::numeric_limits<Type>::is_integer
which was passed to enable_if
, exposed by enable_if
via its ::type
declaration, and then dereferenced by ::type
in the template declaration.
As the latter will not match any expression, the former remains. Therefore the signature is actually is_odd<Integer, typename = bool>(Integer value)
, where the second template parameter may be any type and is ignored if specified.
These compile but do not constrain the type.
template <typename Integer, typename = enable_if_t<std::numeric_limits<Integer>::is_integer>>
bool is_odd(Integer value)
{
return (value % 2) != 0;
}
template <typename Integer, enable_if<std::numeric_limits<Integer>::is_integer>::type = true>
bool is_odd(Integer value)
{
return (value % 2) != 0;
}
These reduce respectively to the following, under all conditions.
template <typename Integer, typename = bool>
bool is_odd(Integer value)
{
return (value % 2) != 0;
}
template <typename Integer, bool = true>
bool is_odd(Integer value)
{
return (value % 2) != 0;
}
Consequently, any type of value passed to is_odd(Type value)
will compile if there is a Type % 2
operator overload for the type. Natively this includes all arithmetic types (including char and floating point) but may include others.
Users | Developers | License | Copyright © 2011-2024 libbitcoin developers
- Home
- manifesto
- libbitcoin.info
- Libbitcoin Institute
- Freenode (IRC)
- Mailing List
- Slack Channel
- Build Libbitcoin
- Comprehensive Overview
- Developer Documentation
- Tutorials (aaronjaramillo)
- Bitcoin Unraveled
-
Cryptoeconomics
- Foreword by Amir Taaki
- Value Proposition
- Axiom of Resistance
- Money Taxonomy
- Pure Bank
- Production and Consumption
- Labor and Leisure
- Custodial Risk Principle
- Dedicated Cost Principle
- Depreciation Principle
- Expression Principle
- Inflation Principle
- Other Means Principle
- Patent Resistance Principle
- Risk Sharing Principle
- Reservation Principle
- Scalability Principle
- Subjective Inflation Principle
- Consolidation Principle
- Fragmentation Principle
- Permissionless Principle
- Public Data Principle
- Social Network Principle
- State Banking Principle
- Substitution Principle
- Cryptodynamic Principles
- Censorship Resistance Property
- Consensus Property
- Stability Property
- Utility Threshold Property
- Zero Sum Property
- Threat Level Paradox
- Miner Business Model
- Qualitative Security Model
- Proximity Premium Flaw
- Variance Discount Flaw
- Centralization Risk
- Pooling Pressure Risk
- ASIC Monopoly Fallacy
- Auditability Fallacy
- Balance of Power Fallacy
- Blockchain Fallacy
- Byproduct Mining Fallacy
- Causation Fallacy
- Cockroach Fallacy
- Credit Expansion Fallacy
- Debt Loop Fallacy
- Decoupled Mining Fallacy
- Dumping Fallacy
- Empty Block Fallacy
- Energy Exhaustion Fallacy
- Energy Store Fallacy
- Energy Waste Fallacy
- Fee Recovery Fallacy
- Genetic Purity Fallacy
- Full Reserve Fallacy
- Halving Fallacy
- Hoarding Fallacy
- Hybrid Mining Fallacy
- Ideal Money Fallacy
- Impotent Mining Fallacy
- Inflation Fallacy
- Inflationary Quality Fallacy
- Jurisdictional Arbitrage Fallacy
- Lunar Fallacy
- Network Effect Fallacy
- Prisoner's Dilemma Fallacy
- Private Key Fallacy
- Proof of Cost Fallacy
- Proof of Memory Façade
- Proof of Stake Fallacy
- Proof of Work Fallacy
- Regression Fallacy
- Relay Fallacy
- Replay Protection Fallacy
- Reserve Currency Fallacy
- Risk Free Return Fallacy
- Scarcity Fallacy
- Selfish Mining Fallacy
- Side Fee Fallacy
- Split Credit Expansion Fallacy
- Stock to Flow Fallacy
- Thin Air Fallacy
- Time Preference Fallacy
- Unlendable Money Fallacy
- Fedcoin Objectives
- Hearn Error
- Collectible Tautology
- Price Estimation
- Savings Relation
- Speculative Consumption
- Spam Misnomer
- Efficiency Paradox
- Split Speculator Dilemma
- Bitcoin Labels
- Brand Arrogation
- Reserve Definition
- Maximalism Definition
- Shitcoin Definition
- Glossary
- Console Applications
- Development Libraries
- Maintainer Information
- Miscellaneous Articles