Concepts Lite
Generic programming
template<typename Duck>
void talk(const Duck& duck)
{
duck.quack();
}
talk(10); ???
Dado un tipo cualquiera T...
- ¿Soporta T las operaciones que necesito?
- ¿Implementa T las semánticas que necesito?
template<typename T>
T twice(const T& value)
{
return value * 2;
}
Y si...
- ¿Pudieramos comprobar qué operaciones soporta un tipo?
- ¿Pudieramos especificar cuales de esas operaciones debe soportar un parámetro de plantilla?
SFINAE
template<typename...>
using void_t = void;
template<typename T>
struct twiceable :
std::false_type
{};
template<typename T>
struct twiceable<T, void_t<decltype(std::declval<T>() * 2)>> :
std::true_type
{};
SFINAE
template<typename T,
typename = typename std::enable_if<
twiceable<T>::value
>::type>
T twice(const T& value)
{
return value * 2;
}
int i = twice(2);
SFINAE
- Tricky
- Nada intuitivo
- Errores de compilación ininteligibles
Conceptos
Concepto
- Conjunto de propiedades sintácticas y semánticas que caracterizan a un conjunto de tipos
- Concepts Lite: Sintaxis para declarar un concepto en base a propiedades sintácticas
Function concept
template<typename T>
concept bool Type()
{
return true;
}
- Predicado lógico sobre uno o mas tipos
- concept keyword
- Implícitamente constexpr e inline
Variable concept
template<typename T>
concept bool Type = true;
- El predicado se representa mediante una plantilla de variable (C++14)
Expresión requires
template<typename T>
concept bool A = requires(T a)
{
...
};
template<typename T, typename U>
concept bool A = requires(T a, const U& b)
{
...
};
template<typename... Ts>
concept bool C = requires(Ts... ts)
{
...
};
"Sandbox" donde pedir valores con los que poder trabajar. Sustituye a std::declval<T>()
Requisitos
template<typename A, typename B = A>
concept bool Addable = requires(const A& a, const B& b)
{
a + b; // a + b es válido
// (existe operator+(A,B))
{a + b} -> int; // a + b es válido
// y devuelve int
{a + b} -> Addable; // a + b es válido
// y devuelve tipo que satisface Addable
};
- Requisitos simples
- Requisitos compuestos
Restricciones
template<typename T>
concept bool Iterable = requires()
{
typename T::iterator; // T tiene tipo miembro "iterator"
typename std::iterator_traits<T>; // Instancia de std::iterator_traits es válida
requires Iterator<typename T::iterator>; // T::iterator satisface el concepto "Iterator"
};
- Requisitos de tipo
- Requisitos anidados
Usando conceptos
Como tipo de variables
template<typename Map, typename Key>
auto search(const Map& map, const Key& key)
{
// it es de un tipo que satisface "Iterator"
Iterator it = map.find(key);
...
}
- Como un auto "más concreto"
Cláusula requires
template<typename R, typename F>
requires Iterable<R> && Invokable<F, typename R::value_type>
void foreach(const R& range, F function)
{
for(const auto& e : range)
function(e);
}
- Permite establecer qué conceptos deben cumplir los parámetros de plantilla
Cláusula requires
template<typename A, typename B>
requires requires(A a, B b) { a + b; }
auto add(const A& a, const B& b)
{
return a + b;
}
- In-place requires expression: Podemos definir constraints en el mismo sitio donde los usamos.
Plantillas abreviadas
template<typename T>
concept bool StreamableOut = requires(std::ostream& os, const T& value)
{
{ os << value } -> std::ostream&;
}
namespace boost
{
std::string lexical_cast(const StreamableOut& value)
{
std::ostringstream os;
os << value;
return os.str();
}
}
- Usamos el nombre del concepto a satisfacer como tipo del parámetro
- Ojo! Sigue siendo una plantilla
Plantillas abreviadas
template<typename T>
concept bool DefaultConstructible = std::is_default_constructible<T>::value;
template<typename T, typename Alloc>
concept bool Allocator = is_instance_of<Alloc, T>::value &&
requires(const Alloc& alloc)
{
alloc.allocate(1);
...
}
template<DefaultConstructible T, Allocator<T> Alloc>
class vector
{
...
};
- Por supuesto con plantillas de clase también funciona
Introducción a plantilla
template<typename F, typename... Args>
concept bool Invokable = requires(F f, Args... args)
{
f(args...);
}
Invokable{F, Args...}
auto invoke(F f, Args&&... args)
{
return f(std::forward<Args>(args)...);
}
- Más azúcar: Concepto C de n parámetros T1, ..., Tn
Ejemplo: Iteradores
Iterator
template<typename It>
concept bool Iterator = requires(It lvalue)
{
requires CopyConstructible<It>;
requires CopyAssignable<It>;
requires Destructible<It>;
requires Swappable<It>;
*lvalue;
{++lvalue} -> It&
};
ForwardIterator
template<typename It>
concept bool ForwardIterator = requires(It i)
{
requires InputIterator<It>;
requires DefaultConstructible<It>;
requires crab::Switch()
.Case(OutputIterator<It, detail::value_type<It>>)
.Then(Same<detail::value_type<It>&, detail::reference<It>>)
.Default()
.Then(Same<const detail::value_type<It>&, detail::reference<It>>)
();
{i++} -> It;
{*i++} -> detail::reference<It>;
};
Probando Concepts Lite
- Implementación experimental en GCC
- https://github.com/Manu343726/gcc_concepts_bugs
- Imagen de docker con GCC trunk ya compilado
- Ejemplos
- Integración contínua
¿Preguntas?
Concepts Lite
By Manu Sánchez
Concepts Lite
- 1,196