Commit 90684ac4 authored by Ashwin Srinath's avatar Ashwin Srinath Committed by GitHub

Add support for forwarding references (GH-3821)

See, for example, https://isocpp.org/blog/2012/11/universal-references-in-c11-scott-meyers

Closes #3814
parent 27616f33
...@@ -504,21 +504,21 @@ class ExprNode(Node): ...@@ -504,21 +504,21 @@ class ExprNode(Node):
else: else:
return self.calculate_result_code() return self.calculate_result_code()
def _make_move_result_rhs(self, result, allow_move=True): def _make_move_result_rhs(self, result, optional=False):
if (self.is_temp and allow_move and if optional and not (self.is_temp and self.type.is_cpp_class and not self.type.is_reference):
self.type.is_cpp_class and not self.type.is_reference):
self.has_temp_moved = True
return "__PYX_STD_MOVE_IF_SUPPORTED({0})".format(result)
else:
return result return result
self.has_temp_moved = True
return "{}({})".format("__PYX_STD_MOVE_IF_SUPPORTED" if optional else "std::move", result)
def move_result_rhs(self): def move_result_rhs(self):
return self._make_move_result_rhs(self.result()) return self._make_move_result_rhs(self.result(), optional=True)
def move_result_rhs_as(self, type): def move_result_rhs_as(self, type):
allow_move = (type and not type.is_reference and not type.needs_refcounting) result = self.result_as(type)
return self._make_move_result_rhs(self.result_as(type), if not (type.is_reference or type.needs_refcounting):
allow_move=allow_move) requires_move = type.is_rvalue_reference and self.is_temp
result = self._make_move_result_rhs(result, optional=not requires_move)
return result
def pythran_result(self, type_=None): def pythran_result(self, type_=None):
if is_pythran_supported_node_or_none(self): if is_pythran_supported_node_or_none(self):
...@@ -5784,8 +5784,18 @@ class SimpleCallNode(CallNode): ...@@ -5784,8 +5784,18 @@ class SimpleCallNode(CallNode):
else: else:
alternatives = overloaded_entry.all_alternatives() alternatives = overloaded_entry.all_alternatives()
entry = PyrexTypes.best_match( # For any argument/parameter pair A/P, if P is a forwarding reference,
[arg.type for arg in args], alternatives, self.pos, env, args) # use lvalue-reference-to-A for deduction in place of A when the
# function call argument is an lvalue. See:
# https://en.cppreference.com/w/cpp/language/template_argument_deduction#Deduction_from_a_function_call
arg_types = [arg.type for arg in args]
if func_type.is_cfunction:
for i, formal_arg in enumerate(func_type.args):
if formal_arg.is_forwarding_reference():
if self.args[i].is_lvalue():
arg_types[i] = PyrexTypes.c_ref_type(arg_types[i])
entry = PyrexTypes.best_match(arg_types, alternatives, self.pos, env, args)
if not entry: if not entry:
self.type = PyrexTypes.error_type self.type = PyrexTypes.error_type
......
...@@ -77,7 +77,7 @@ def make_lexicon(): ...@@ -77,7 +77,7 @@ def make_lexicon():
punct = Any(":,;+-*/|&<>=.%`~^?!@") punct = Any(":,;+-*/|&<>=.%`~^?!@")
diphthong = Str("==", "<>", "!=", "<=", ">=", "<<", ">>", "**", "//", diphthong = Str("==", "<>", "!=", "<=", ">=", "<<", ">>", "**", "//",
"+=", "-=", "*=", "/=", "%=", "|=", "^=", "&=", "+=", "-=", "*=", "/=", "%=", "|=", "^=", "&=",
"<<=", ">>=", "**=", "//=", "->", "@=") "<<=", ">>=", "**=", "//=", "->", "@=", "&&")
spaces = Rep1(Any(" \t\f")) spaces = Rep1(Any(" \t\f"))
escaped_newline = Str("\\\n") escaped_newline = Str("\\\n")
lineterm = Eol + Opt(Str("\n")) lineterm = Eol + Opt(Str("\n"))
......
...@@ -547,9 +547,7 @@ class CPtrDeclaratorNode(CDeclaratorNode): ...@@ -547,9 +547,7 @@ class CPtrDeclaratorNode(CDeclaratorNode):
return self.base.analyse(ptr_type, env, nonempty=nonempty, visibility=visibility, in_pxd=in_pxd) return self.base.analyse(ptr_type, env, nonempty=nonempty, visibility=visibility, in_pxd=in_pxd)
class CReferenceDeclaratorNode(CDeclaratorNode): class _CReferenceDeclaratorBaseNode(CDeclaratorNode):
# base CDeclaratorNode
child_attrs = ["base"] child_attrs = ["base"]
def declared_name(self): def declared_name(self):
...@@ -558,6 +556,8 @@ class CReferenceDeclaratorNode(CDeclaratorNode): ...@@ -558,6 +556,8 @@ class CReferenceDeclaratorNode(CDeclaratorNode):
def analyse_templates(self): def analyse_templates(self):
return self.base.analyse_templates() return self.base.analyse_templates()
class CReferenceDeclaratorNode(_CReferenceDeclaratorBaseNode):
def analyse(self, base_type, env, nonempty=0, visibility=None, in_pxd=False): def analyse(self, base_type, env, nonempty=0, visibility=None, in_pxd=False):
if base_type.is_pyobject: if base_type.is_pyobject:
error(self.pos, "Reference base type cannot be a Python object") error(self.pos, "Reference base type cannot be a Python object")
...@@ -565,6 +565,14 @@ class CReferenceDeclaratorNode(CDeclaratorNode): ...@@ -565,6 +565,14 @@ class CReferenceDeclaratorNode(CDeclaratorNode):
return self.base.analyse(ref_type, env, nonempty=nonempty, visibility=visibility, in_pxd=in_pxd) return self.base.analyse(ref_type, env, nonempty=nonempty, visibility=visibility, in_pxd=in_pxd)
class CppRvalueReferenceDeclaratorNode(_CReferenceDeclaratorBaseNode):
def analyse(self, base_type, env, nonempty=0, visibility=None, in_pxd=False):
if base_type.is_pyobject:
error(self.pos, "Rvalue-reference base type cannot be a Python object")
ref_type = PyrexTypes.cpp_rvalue_ref_type(base_type)
return self.base.analyse(ref_type, env, nonempty=nonempty, visibility=visibility, in_pxd=in_pxd)
class CArrayDeclaratorNode(CDeclaratorNode): class CArrayDeclaratorNode(CDeclaratorNode):
# base CDeclaratorNode # base CDeclaratorNode
# dimension ExprNode # dimension ExprNode
...@@ -779,6 +787,13 @@ class CFuncDeclaratorNode(CDeclaratorNode): ...@@ -779,6 +787,13 @@ class CFuncDeclaratorNode(CDeclaratorNode):
error(self.pos, "cannot have both '%s' and '%s' " error(self.pos, "cannot have both '%s' and '%s' "
"calling conventions" % (current, callspec)) "calling conventions" % (current, callspec))
func_type.calling_convention = callspec func_type.calling_convention = callspec
if func_type.return_type.is_rvalue_reference:
warning(self.pos, "Rvalue-reference as function return type not supported", 1)
for arg in func_type.args:
if arg.type.is_rvalue_reference and not arg.is_forwarding_reference():
warning(self.pos, "Rvalue-reference as function argument not supported", 1)
return self.base.analyse(func_type, env, visibility=visibility, in_pxd=in_pxd) return self.base.analyse(func_type, env, visibility=visibility, in_pxd=in_pxd)
def declare_optional_arg_struct(self, func_type, env, fused_cname=None): def declare_optional_arg_struct(self, func_type, env, fused_cname=None):
...@@ -1389,6 +1404,8 @@ class CVarDefNode(StatNode): ...@@ -1389,6 +1404,8 @@ class CVarDefNode(StatNode):
return return
if type.is_reference and self.visibility != 'extern': if type.is_reference and self.visibility != 'extern':
error(declarator.pos, "C++ references cannot be declared; use a pointer instead") error(declarator.pos, "C++ references cannot be declared; use a pointer instead")
if type.is_rvalue_reference and self.visibility != 'extern':
error(declarator.pos, "C++ rvalue-references cannot be declared")
if type.is_cfunction: if type.is_cfunction:
if 'staticmethod' in env.directives: if 'staticmethod' in env.directives:
type.is_static_method = True type.is_static_method = True
......
...@@ -2876,12 +2876,13 @@ def p_c_simple_declarator(s, ctx, empty, is_type, cmethod_flag, ...@@ -2876,12 +2876,13 @@ def p_c_simple_declarator(s, ctx, empty, is_type, cmethod_flag,
if is_ptrptr: if is_ptrptr:
base = Nodes.CPtrDeclaratorNode(pos, base=base) base = Nodes.CPtrDeclaratorNode(pos, base=base)
result = Nodes.CPtrDeclaratorNode(pos, base=base) result = Nodes.CPtrDeclaratorNode(pos, base=base)
elif s.sy == '&': elif s.sy == '&' or (s.sy == '&&' and s.context.cpp):
node_class = Nodes.CppRvalueReferenceDeclaratorNode if s.sy == '&&' else Nodes.CReferenceDeclaratorNode
s.next() s.next()
base = p_c_declarator(s, ctx, empty = empty, is_type = is_type, base = p_c_declarator(s, ctx, empty=empty, is_type=is_type,
cmethod_flag = cmethod_flag, cmethod_flag=cmethod_flag,
assignable = assignable, nonempty = nonempty) assignable=assignable, nonempty=nonempty)
result = Nodes.CReferenceDeclaratorNode(pos, base = base) result = node_class(pos, base=base)
else: else:
rhs = None rhs = None
if s.sy == 'IDENT': if s.sy == 'IDENT':
......
...@@ -175,6 +175,7 @@ class PyrexType(BaseType): ...@@ -175,6 +175,7 @@ class PyrexType(BaseType):
# is_ptr boolean Is a C pointer type # is_ptr boolean Is a C pointer type
# is_null_ptr boolean Is the type of NULL # is_null_ptr boolean Is the type of NULL
# is_reference boolean Is a C reference type # is_reference boolean Is a C reference type
# is_rvalue_reference boolean Is a C++ rvalue reference type
# is_const boolean Is a C const type # is_const boolean Is a C const type
# is_volatile boolean Is a C volatile type # is_volatile boolean Is a C volatile type
# is_cv_qualified boolean Is a C const or volatile type # is_cv_qualified boolean Is a C const or volatile type
...@@ -241,6 +242,7 @@ class PyrexType(BaseType): ...@@ -241,6 +242,7 @@ class PyrexType(BaseType):
is_ptr = 0 is_ptr = 0
is_null_ptr = 0 is_null_ptr = 0
is_reference = 0 is_reference = 0
is_rvalue_reference = 0
is_const = 0 is_const = 0
is_volatile = 0 is_volatile = 0
is_cv_qualified = 0 is_cv_qualified = 0
...@@ -2751,26 +2753,17 @@ class CNullPtrType(CPtrType): ...@@ -2751,26 +2753,17 @@ class CNullPtrType(CPtrType):
is_null_ptr = 1 is_null_ptr = 1
class CReferenceType(BaseType): class CReferenceBaseType(BaseType):
is_reference = 1
is_fake_reference = 0 is_fake_reference = 0
# Common base type for C reference and C++ rvalue reference types.
def __init__(self, base_type): def __init__(self, base_type):
self.ref_base_type = base_type self.ref_base_type = base_type
def __repr__(self): def __repr__(self):
return "<CReferenceType %s>" % repr(self.ref_base_type) return "<%s %s>" % repr(self.__class__.__name__, self.ref_base_type)
def __str__(self):
return "%s &" % self.ref_base_type
def declaration_code(self, entity_code,
for_display = 0, dll_linkage = None, pyrex = 0):
#print "CReferenceType.declaration_code: pointer to", self.base_type ###
return self.ref_base_type.declaration_code(
"&%s" % entity_code,
for_display, dll_linkage, pyrex)
def specialize(self, values): def specialize(self, values):
base_type = self.ref_base_type.specialize(values) base_type = self.ref_base_type.specialize(values)
...@@ -2786,13 +2779,25 @@ class CReferenceType(BaseType): ...@@ -2786,13 +2779,25 @@ class CReferenceType(BaseType):
return getattr(self.ref_base_type, name) return getattr(self.ref_base_type, name)
class CReferenceType(CReferenceBaseType):
is_reference = 1
def __str__(self):
return "%s &" % self.ref_base_type
def declaration_code(self, entity_code,
for_display = 0, dll_linkage = None, pyrex = 0):
#print "CReferenceType.declaration_code: pointer to", self.base_type ###
return self.ref_base_type.declaration_code(
"&%s" % entity_code,
for_display, dll_linkage, pyrex)
class CFakeReferenceType(CReferenceType): class CFakeReferenceType(CReferenceType):
is_fake_reference = 1 is_fake_reference = 1
def __repr__(self):
return "<CFakeReferenceType %s>" % repr(self.ref_base_type)
def __str__(self): def __str__(self):
return "%s [&]" % self.ref_base_type return "%s [&]" % self.ref_base_type
...@@ -2802,6 +2807,20 @@ class CFakeReferenceType(CReferenceType): ...@@ -2802,6 +2807,20 @@ class CFakeReferenceType(CReferenceType):
return "__Pyx_FakeReference<%s> %s" % (self.ref_base_type.empty_declaration_code(), entity_code) return "__Pyx_FakeReference<%s> %s" % (self.ref_base_type.empty_declaration_code(), entity_code)
class CppRvalueReferenceType(CReferenceBaseType):
is_rvalue_reference = 1
def __str__(self):
return "%s &&" % self.ref_base_type
def declaration_code(self, entity_code,
for_display = 0, dll_linkage = None, pyrex = 0):
return self.ref_base_type.declaration_code(
"&&%s" % entity_code,
for_display, dll_linkage, pyrex)
class CFuncType(CType): class CFuncType(CType):
# return_type CType # return_type CType
# args [CFuncTypeArg] # args [CFuncTypeArg]
...@@ -3434,6 +3453,12 @@ class CFuncTypeArg(BaseType): ...@@ -3434,6 +3453,12 @@ class CFuncTypeArg(BaseType):
def specialize(self, values): def specialize(self, values):
return CFuncTypeArg(self.name, self.type.specialize(values), self.pos, self.cname) return CFuncTypeArg(self.name, self.type.specialize(values), self.pos, self.cname)
def is_forwarding_reference(self):
if self.type.is_rvalue_reference:
if (isinstance(self.type.ref_base_type, TemplatePlaceholderType)
and not self.type.ref_base_type.is_cv_qualified):
return True
return False
class ToPyStructUtilityCode(object): class ToPyStructUtilityCode(object):
...@@ -4925,6 +4950,10 @@ def c_ref_type(base_type): ...@@ -4925,6 +4950,10 @@ def c_ref_type(base_type):
# Construct a C reference type # Construct a C reference type
return _construct_type_from_base(CReferenceType, base_type) return _construct_type_from_base(CReferenceType, base_type)
def cpp_rvalue_ref_type(base_type):
# Construct a C++ rvalue reference type
return _construct_type_from_base(CppRvalueReferenceType, base_type)
def c_const_type(base_type): def c_const_type(base_type):
# Construct a C const type. # Construct a C const type.
return _construct_type_from_base(CConstType, base_type) return _construct_type_from_base(CConstType, base_type)
......
# tag: cpp, cpp11
# mode: compile
cdef extern from *:
"""
template <typename T>
void accept(T&& x) {}
"""
cdef void accept[T](T&& x)
cdef int make_int_py() except *:
# might raise Python exception (thus needs a temp)
return 1
cdef int make_int_cpp() except +:
# might raise C++ exception (thus needs a temp)
return 1
def test_func_arg():
# won't compile if move() isn't called on the temp:
accept(make_int_py())
accept(make_int_cpp())
# mode: error
# tag: werror, cpp, cpp11
# These tests check for unsupported use of rvalue-references (&&)
# and should be removed or cleaned up when support is added.
cdef int&& x
cdef void foo(int&& x):
pass
cdef int&& bar():
pass
cdef extern from *:
"""
void baz(int x, int&& y) {}
template <typename T>
void qux(const T&& x) {}
"""
cdef void baz(int x, int&& y)
cdef void qux[T](const T&& x)
_ERRORS="""
7:8: C++ rvalue-references cannot be declared
9:13: Rvalue-reference as function argument not supported
12:14: Rvalue-reference as function return type not supported
22:17: Rvalue-reference as function argument not supported
23:20: Rvalue-reference as function argument not supported
"""
# mode: error
# tag: cpp, cpp11
cdef foo(object& x): pass
cdef bar(object&& x): pass
_ERRORS="""
4:15: Reference base type cannot be a Python object
5:15: Rvalue-reference base type cannot be a Python object
"""
# mode: run
# tag: cpp, cpp11
from libcpp.utility cimport move
cdef extern from *:
"""
#include <utility>
const char* f(int& x) {
return "lvalue-ref";
}
const char* f(int&& x) {
return "rvalue-ref";
}
template <typename T>
const char* foo(T&& x)
{
return f(std::forward<T>(x));
}
"""
const char* foo[T](T&& x)
def test_forwarding_ref():
"""
>>> test_forwarding_ref()
"""
cdef int x = 1
assert foo(x) == b"lvalue-ref"
assert foo(<int>(1)) == b"rvalue-ref"
assert foo(move(x)) == b"rvalue-ref"
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment